Skip to content

Commit 362c5ed

Browse files
OttoAllmendingerllm-git
andcommitted
feat(utxo-lib): extend fixture util with type-safe functions
Add support for different file formats (.json, .hex, .txt) with proper type handling in fixture loading. Replace fs-extra with native fs.promises for better modularity. Issue: BTC-2651 Co-authored-by: llm-git <[email protected]>
1 parent 04317c7 commit 362c5ed

File tree

1 file changed

+98
-5
lines changed

1 file changed

+98
-5
lines changed

modules/utxo-lib/test/fixture.util.ts

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as assert from 'assert';
2+
import * as fs from 'fs';
23
import * as mpath from 'path';
3-
import * as fs from 'fs-extra';
44

55
function bufferAsHex(v: unknown): unknown {
66
// You would think that you could use `Buffer.isBuffer(v)` here but you would be mistaken
@@ -21,16 +21,16 @@ export async function readFixture<T>(path: string, defaultValue: T): Promise<T>
2121
path = path.replace('bitcoinTestnet4', 'bitcoinPublicSignet');
2222

2323
try {
24-
await fs.access(mpath.dirname(path));
24+
await fs.promises.access(mpath.dirname(path));
2525
} catch (e) {
26-
await fs.mkdirp(mpath.dirname(path));
26+
await fs.promises.mkdir(mpath.dirname(path), { recursive: true });
2727
}
2828

2929
try {
30-
return JSON.parse(await fs.readFile(path, 'utf8')) as T;
30+
return JSON.parse(await fs.promises.readFile(path, 'utf8')) as T;
3131
} catch (e) {
3232
if (e.code === 'ENOENT') {
33-
await fs.writeFile(path, toPrettyJSON(defaultValue));
33+
await fs.promises.writeFile(path, toPrettyJSON(defaultValue));
3434
throw new Error(`wrote defaults, please check contents and re-run tests`);
3535
}
3636

@@ -46,3 +46,96 @@ export async function readFixture<T>(path: string, defaultValue: T): Promise<T>
4646
export function assertEqualJSON<T>(a: T, b: T): void {
4747
assert.deepStrictEqual(JSON.parse(toPrettyJSON(a)), JSON.parse(toPrettyJSON(b)));
4848
}
49+
50+
type FixtureEncoding = 'json' | 'hex' | 'txt';
51+
52+
function isNodeJsError(e: unknown): e is NodeJS.ErrnoException {
53+
return e instanceof Error && typeof (e as NodeJS.ErrnoException).code === 'string';
54+
}
55+
56+
function fixtureEncoding(path: string): FixtureEncoding {
57+
if (path.endsWith('.json')) {
58+
return 'json';
59+
}
60+
if (path.endsWith('.hex')) {
61+
return 'hex';
62+
}
63+
if (path.endsWith('.txt')) {
64+
return 'txt';
65+
}
66+
throw new Error(`unknown fixture encoding for ${path}`);
67+
}
68+
69+
function decodeFixture(raw: string, encoding: FixtureEncoding): unknown {
70+
switch (encoding) {
71+
case 'json':
72+
return JSON.parse(raw);
73+
case 'hex':
74+
return Buffer.from(raw, 'hex');
75+
case 'txt':
76+
return raw;
77+
}
78+
}
79+
80+
function encodeFixture(value: unknown, encoding: FixtureEncoding): string {
81+
switch (encoding) {
82+
case 'json':
83+
return JSON.stringify(value, null, 2) + '\n';
84+
case 'hex':
85+
if (!Buffer.isBuffer(value)) {
86+
throw new Error(`expected Buffer, got ${typeof value}`);
87+
}
88+
return value.toString('hex');
89+
case 'txt':
90+
if (typeof value !== 'string') {
91+
throw new Error(`expected string, got ${typeof value}`);
92+
}
93+
return value;
94+
}
95+
}
96+
97+
/**
98+
* Return fixture described in `path`.
99+
*
100+
* If file does not exist and `defaultValue` is provided, writes defaultValue to `path` and throws an error.
101+
*
102+
* @param path
103+
* @param defaultValue
104+
* @return T - fixture content
105+
*/
106+
export async function getFixture<T>(path: string, defaultValue?: T | (() => Promise<T>)): Promise<T> {
107+
try {
108+
await fs.promises.stat(mpath.dirname(path));
109+
} catch (e) {
110+
if (isNodeJsError(e) && e.code === 'ENOENT') {
111+
throw new Error(`fixture directory ${mpath.dirname(path)} not found, please create it first`);
112+
}
113+
throw e;
114+
}
115+
116+
const encoding = fixtureEncoding(path);
117+
118+
try {
119+
return decodeFixture(await fs.promises.readFile(path, 'utf8'), encoding) as T;
120+
} catch (e) {
121+
if (isNodeJsError(e) && e.code === 'ENOENT') {
122+
if (process.env.WRITE_FIXTURES === '0') {
123+
throw new Error(`fixture ${path} not found, WRITE_FIXTURES=0`);
124+
}
125+
if (defaultValue === undefined) {
126+
throw new Error(`fixture ${path} not found and no default value given`);
127+
}
128+
if (typeof defaultValue === 'function') {
129+
defaultValue = await (defaultValue as () => Promise<T>)();
130+
}
131+
await fs.promises.writeFile(path, encodeFixture(defaultValue, encoding));
132+
throw new Error(`wrote default value for ${path}, please inspect and restart test`);
133+
}
134+
135+
throw e;
136+
}
137+
}
138+
139+
export function jsonNormalize<T>(v: T): T {
140+
return JSON.parse(JSON.stringify(v)) as T;
141+
}

0 commit comments

Comments
 (0)