65f7179557
CRITICAL fixes: - Stop passing mint URLs as relay URLs in initNDK (protocol mismatch) - Add unit tests for buildTransactionHistory (dedup, fallback, sort) HIGH fixes: - Restore .claude entry in .gitignore - Add warning logging for skipped malformed proof tags in nutzap parsing - Add error handling + 30s timeout to NDK initialization - Add unit tests for formatCSV and escapeField - Export escapeField for testability Also: add test script to package.json, include test/ in tsconfig Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
220 lines
5.0 KiB
TypeScript
220 lines
5.0 KiB
TypeScript
import { describe, it, expect } from "bun:test";
|
|
import { buildTransactionHistory } from "../src/events.js";
|
|
import type {
|
|
WalletData,
|
|
TokenData,
|
|
HistoryEntry,
|
|
TransactionRecord,
|
|
} from "../src/types.js";
|
|
|
|
describe("buildTransactionHistory", () => {
|
|
const emptyWallet: WalletData = { mints: [], privkey: "" };
|
|
|
|
it("should build records from history events", () => {
|
|
const history: HistoryEntry[] = [
|
|
{
|
|
direction: "in",
|
|
amount: "100",
|
|
unit: "sat",
|
|
eventId: "evt1",
|
|
createdAt: 1000,
|
|
referencedEvents: [],
|
|
},
|
|
{
|
|
direction: "out",
|
|
amount: "50",
|
|
unit: "sat",
|
|
eventId: "evt2",
|
|
createdAt: 2000,
|
|
referencedEvents: [],
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(emptyWallet, [], history, [], []);
|
|
|
|
expect(records).toHaveLength(2);
|
|
expect(records[0].type).toBe("receive");
|
|
expect(records[0].amount).toBe(100);
|
|
expect(records[1].type).toBe("send");
|
|
expect(records[1].amount).toBe(50);
|
|
});
|
|
|
|
it("should deduplicate by event ID across sources", () => {
|
|
const history: HistoryEntry[] = [
|
|
{
|
|
direction: "in",
|
|
amount: "100",
|
|
unit: "sat",
|
|
eventId: "same-id",
|
|
createdAt: 1000,
|
|
referencedEvents: [],
|
|
},
|
|
];
|
|
const nutzaps: TransactionRecord[] = [
|
|
{
|
|
date: "2025-01-01T00:00:00Z",
|
|
type: "zap",
|
|
amount: 100,
|
|
unit: "sat",
|
|
mint: "",
|
|
token_id: "same-id",
|
|
memo: "",
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(
|
|
emptyWallet,
|
|
[],
|
|
history,
|
|
nutzaps,
|
|
[]
|
|
);
|
|
expect(records).toHaveLength(1);
|
|
});
|
|
|
|
it("should fall back to token events when no history events exist", () => {
|
|
const tokens: TokenData[] = [
|
|
{
|
|
mint: "https://mint.example",
|
|
unit: "sat",
|
|
proofs: [{ id: "p1", amount: 64, secret: "s", C: "c" }],
|
|
eventId: "tok1",
|
|
createdAt: 1000,
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(emptyWallet, tokens, [], [], []);
|
|
expect(records).toHaveLength(1);
|
|
expect(records[0].amount).toBe(64);
|
|
expect(records[0].type).toBe("receive");
|
|
});
|
|
|
|
it("should NOT use token events when history events exist", () => {
|
|
const tokens: TokenData[] = [
|
|
{
|
|
mint: "https://mint.example",
|
|
unit: "sat",
|
|
proofs: [{ id: "p1", amount: 64, secret: "s", C: "c" }],
|
|
eventId: "tok1",
|
|
createdAt: 1000,
|
|
},
|
|
];
|
|
const history: HistoryEntry[] = [
|
|
{
|
|
direction: "in",
|
|
amount: "100",
|
|
unit: "sat",
|
|
eventId: "evt1",
|
|
createdAt: 2000,
|
|
referencedEvents: [],
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(
|
|
emptyWallet,
|
|
tokens,
|
|
history,
|
|
[],
|
|
[]
|
|
);
|
|
expect(records).toHaveLength(1);
|
|
expect(records[0].token_id).toBe("evt1");
|
|
});
|
|
|
|
it("should sort records by date ascending", () => {
|
|
const nutzaps: TransactionRecord[] = [
|
|
{
|
|
date: new Date(3000 * 1000).toISOString(),
|
|
type: "zap",
|
|
amount: 10,
|
|
unit: "sat",
|
|
mint: "",
|
|
token_id: "z1",
|
|
memo: "",
|
|
},
|
|
{
|
|
date: new Date(1000 * 1000).toISOString(),
|
|
type: "zap",
|
|
amount: 20,
|
|
unit: "sat",
|
|
mint: "",
|
|
token_id: "z2",
|
|
memo: "",
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(
|
|
emptyWallet,
|
|
[],
|
|
[],
|
|
nutzaps,
|
|
[]
|
|
);
|
|
expect(records[0].token_id).toBe("z2");
|
|
expect(records[1].token_id).toBe("z1");
|
|
});
|
|
|
|
it("should handle non-numeric amount gracefully (defaults to 0)", () => {
|
|
const history: HistoryEntry[] = [
|
|
{
|
|
direction: "in",
|
|
amount: "not-a-number",
|
|
unit: "sat",
|
|
eventId: "evt1",
|
|
createdAt: 1000,
|
|
referencedEvents: [],
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(emptyWallet, [], history, [], []);
|
|
expect(records[0].amount).toBe(0);
|
|
});
|
|
|
|
it("should merge nutzaps and redemptions with history", () => {
|
|
const history: HistoryEntry[] = [
|
|
{
|
|
direction: "in",
|
|
amount: "100",
|
|
unit: "sat",
|
|
eventId: "h1",
|
|
createdAt: 1000,
|
|
referencedEvents: [],
|
|
},
|
|
];
|
|
const nutzaps: TransactionRecord[] = [
|
|
{
|
|
date: new Date(2000 * 1000).toISOString(),
|
|
type: "zap",
|
|
amount: 50,
|
|
unit: "sat",
|
|
mint: "",
|
|
token_id: "z1",
|
|
memo: "",
|
|
},
|
|
];
|
|
const redemptions: TransactionRecord[] = [
|
|
{
|
|
date: new Date(3000 * 1000).toISOString(),
|
|
type: "receive",
|
|
amount: 50,
|
|
unit: "sat",
|
|
mint: "",
|
|
token_id: "r1",
|
|
memo: "nutzap redemption",
|
|
},
|
|
];
|
|
|
|
const records = buildTransactionHistory(
|
|
emptyWallet,
|
|
[],
|
|
history,
|
|
nutzaps,
|
|
redemptions
|
|
);
|
|
expect(records).toHaveLength(3);
|
|
expect(records[0].token_id).toBe("h1");
|
|
expect(records[1].token_id).toBe("z1");
|
|
expect(records[2].token_id).toBe("r1");
|
|
});
|
|
});
|