KeiSeiKit-1.0/_primitives/_rust/kei-ledger/tests/v7_micro.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

133 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! v7 micro-cents column tests (Wave 44c, 2026-04-24).
//!
//! Constructor Pattern: extracted from `v6_cost.rs` so each test file
//! stays under the 200-LOC ceiling. Loads source modules via `#[path]`
//! to avoid forcing all callers through the public lib API.
#[path = "../src/migrations_list.rs"]
mod migrations_list;
#[path = "../src/schema.rs"]
mod schema;
#[path = "../src/error.rs"]
mod error;
#[path = "../src/row.rs"]
mod row;
#[path = "../src/ledger.rs"]
mod ledger;
#[path = "../src/descendants.rs"]
mod descendants;
#[path = "../src/cost.rs"]
mod cost;
use rusqlite::Connection;
use tempfile::TempDir;
fn open_tmp() -> (TempDir, Connection) {
let dir = tempfile::tempdir().unwrap();
let db = dir.path().join("ledger.sqlite");
let conn = ledger::open(&db).unwrap();
(dir, conn)
}
/// v7-T8: schema reaches at least v7 from a fresh DB; cost_micro_cents
/// column exists with DEFAULT 0 for new rows and backfill SQL is harmless.
/// Uses `>= 7` rather than `== 7` so future migrations (currently at v8)
/// don't break this v7-specific assertion.
#[test]
fn migration_v7_adds_micro_cents_column() {
let (_d, conn) = open_tmp();
let v: i64 = conn
.query_row("PRAGMA user_version", [], |r| r.get(0))
.unwrap();
assert!(v >= 7, "schema must be at least v7, got v{v}");
ledger::fork(&conn, "v7", "br-v7", None, "sha", None, None, None, None).unwrap();
let micro: i64 = conn
.query_row(
"SELECT cost_micro_cents FROM agents WHERE id = 'v7'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(micro, 0, "fresh row defaults to 0 micro-cents");
}
/// v7-T9: pre-v7 row carrying a non-zero cost_cents value gets backfilled
/// to (cost_cents × 1_000_000) micro-cents on migration.
#[test]
fn migration_v7_backfills_pre_existing_cost_cents() {
let dir = tempfile::tempdir().unwrap();
let db = dir.path().join("ledger.sqlite");
// Create at v6 manually by invoking only the first 6 migrations,
// then mass-insert a row with cost_cents set, THEN reopen so v7
// backfill runs.
{
let conn = rusqlite::Connection::open(&db).unwrap();
for sql in &schema::MIGRATIONS[..6] {
conn.execute_batch(sql).unwrap();
}
conn.pragma_update(None, "user_version", 6_i64).unwrap();
conn.execute(
"INSERT INTO agents (id, branch, spec_sha, status, started_ts, cost_cents)
VALUES ('legacy', 'br-legacy', 'sha', 'done', 1, 250)",
[],
)
.unwrap();
}
let conn = ledger::open(&db).unwrap();
let (cents, micro, _, _) = cost::read_cost_micro(&conn, "legacy").unwrap().unwrap();
assert_eq!(cents, 250);
assert_eq!(micro, 250 * 1_000_000, "backfilled to 250M micro-cents");
}
/// v7-T10: compose_micro_cents is exact under integer overflow guards.
/// 1.5M input + 0.5M output @ 100c/MTok / 500c/MTok = 400M micro-cents.
#[test]
fn compose_micro_cents_exact_arithmetic() {
let m = cost::compose_micro_cents(1_500_000, 500_000, 100, 500);
// 1.5M × 100 = 150M, 0.5M × 500 = 250M, total = 400M micro-cents.
assert_eq!(m, 400_000_000);
}
/// v7-T11: 100 micro-turns of 5 tokens each input @ 1c/MTok do NOT
/// round to 1 cent each; the cents accumulator stays 0 while micro
/// accumulates 500.
#[test]
fn compose_micro_cents_micro_turns_no_rounding_loss() {
let mut total_micro: u64 = 0;
for _ in 0..100 {
let m = cost::compose_micro_cents(5, 0, 1, 0);
total_micro = total_micro.saturating_add(m);
}
assert_eq!(total_micro, 500, "100 × 5 tokens × 1 cent/MTok = 500 micro-cents");
assert_eq!(
cost::display_cents_from_micro(total_micro),
0,
"rounds DOWN to 0 cents — under threshold"
);
}
/// v7-T12: display_cents_from_micro uses half-up rounding at boundaries.
#[test]
fn display_cents_from_micro_half_up_at_boundary() {
assert_eq!(cost::display_cents_from_micro(0), 0);
assert_eq!(cost::display_cents_from_micro(499_999), 0, "below half rounds down");
assert_eq!(cost::display_cents_from_micro(500_000), 1, "exactly half rounds up");
assert_eq!(cost::display_cents_from_micro(999_999), 1);
assert_eq!(cost::display_cents_from_micro(1_000_000), 1);
assert_eq!(cost::display_cents_from_micro(1_500_000), 2);
}
/// v7-T3c: micro-cents accumulator persists alongside cents. 100 calls
/// of 5 micro-cents each (= 500 micro-cents = 0.0005 cents) round-trip
/// without rounding loss in the micro column.
#[test]
fn record_cost_micro_accumulates_without_rounding_loss() {
let (_d, conn) = open_tmp();
ledger::fork(&conn, "mic", "br-mic", None, "sha", None, None, None, None).unwrap();
for _ in 0..100 {
cost::record_cost_micro(&conn, "mic", 0, 5, "anthropic", "claude").unwrap();
}
let (cents, micro, _, _) = cost::read_cost_micro(&conn, "mic").unwrap().unwrap();
assert_eq!(cents, 0, "100 × 0 cents truncates to 0");
assert_eq!(micro, 500, "but micro-cents accumulator is exact");
}