add end to end hygiene test

This commit is contained in:
Emil Lerch 2026-07-06 12:46:57 -07:00
parent 48b422cba4
commit 550fa4fd88
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -1633,3 +1633,67 @@ test "findPriceDateMismatches: duplicate HEAD identity collapses to one entry" {
try std.testing.expectApproxEqAbs(@as(f64, 150), mismatches.items[0].old_price, 0.01);
try std.testing.expectApproxEqAbs(@as(f64, 160), mismatches.items[0].new_price, 0.01);
}
test "runHygieneCheck: Section 6 flags an un-opted-in symbol's split, not an opted-in one" {
const allocator = std.testing.allocator;
const io = std.testing.io;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
// NVDA: held across its 2024 split, NOT opted in -> should flag.
// AMZN: held across its 2022 split, opted in via metadata -> should not.
try tmp.dir.writeFile(io, .{
.sub_path = "portfolio.srf",
.data =
\\#!srfv1
\\symbol::NVDA,shares:num:100,open_date::2020-01-01,open_price:num:40.00,account::Sample Brokerage
\\symbol::AMZN,shares:num:30,open_date::2019-01-01,open_price:num:90.00,account::Sample Brokerage
\\
,
});
// accounts.srf must exist and parse (an empty map is fine).
try tmp.dir.writeFile(io, .{ .sub_path = "accounts.srf", .data = "#!srfv1\n" });
// Per-symbol opt-in: AMZN yes, NVDA absent (not opted in).
try tmp.dir.writeFile(io, .{
.sub_path = "metadata.srf",
.data = "#!srfv1\nsymbol::AMZN,splits_current_through::2024-01-01\n",
});
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const dir_len = try tmp.dir.realPathFile(io, ".", &path_buf);
const dir = path_buf[0..dir_len];
// Seed the split cache: NVDA 10:1 (2024-06-10), AMZN 20:1 (2022-06-06).
var store = zfin.cache.Store.init(io, allocator, dir);
var nvda = [_]zfin.Split{.{ .date = zfin.Date.fromYmd(2024, 6, 10), .numerator = 10, .denominator = 1 }};
store.write(zfin.Split, "NVDA", nvda[0..], .{ .seconds = zfin.cache.Ttl.splits });
var amzn = [_]zfin.Split{.{ .date = zfin.Date.fromYmd(2022, 6, 6), .numerator = 20, .denominator = 1 }};
store.write(zfin.Split, "AMZN", amzn[0..], .{ .seconds = zfin.cache.Ttl.splits });
// No API keys / no server -> hermetic (fetches can't reach a network).
var svc = zfin.DataService.init(io, allocator, .{ .cache_dir = dir });
defer svc.deinit();
var env = try std.testing.environ.createMap(allocator);
defer env.deinit();
const pf_path = try std.fs.path.join(allocator, &.{ dir, "portfolio.srf" });
defer allocator.free(pf_path);
var aw: std.Io.Writer.Allocating = .init(allocator);
defer aw.deinit();
try runHygieneCheck(io, allocator, &env, &svc, pf_path, 3, false, zfin.Date.fromYmd(2026, 1, 1), 1_767_225_600, false, .never, &aw.writer);
const output = aw.written();
try std.testing.expect(std.mem.indexOf(u8, output, "Portfolio hygiene") != null);
// Assert against Section 6 specifically (it's the last section, so
// slice from its header to end) - robust even if NVDA/AMZN surface
// in an earlier section. The un-opted-in NVDA is listed; the
// opted-in AMZN is not.
const sec6_start = std.mem.indexOf(u8, output, "Unhandled stock splits") orelse return error.Section6Missing;
const sec6 = output[sec6_start..];
try std.testing.expect(std.mem.indexOf(u8, sec6, "NVDA") != null);
try std.testing.expect(std.mem.indexOf(u8, sec6, "AMZN") == null);
}