Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions core/testing/__tests__/fixtures.contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";
import { discoverFixtureFiles } from "@/core/testing/fixtureRegistry";

/**
* SDK-upgrade fixture contract (issue #26).
*
* Fixtures are intentionally decentralized — one `fixtures/` directory per
* feature slice, owned by that slice's contributor — so there is no single
* fixtures root to hand-audit after a `@stellar/stellar-sdk` bump. Most
* fixtures call real SDK constructors (`Keypair`, `TransactionBuilder`,
* `xdr.*`) at module-evaluation time rather than hand-typing values, which
* means a renamed export, changed constructor signature, or removed enum
* member throws the moment the fixture module loads — this test just needs
* to load every one of them and surface exactly which file broke.
*
* Deliberately-invalid fixture *values* (e.g. `notAnEnvelopeXdr`,
* `notBase64`) are untouched by this: they're plain strings a slice's own
* tests decode and expect to fail, not something this contract should flag.
*/
describe("fixture SDK-upgrade contract", () => {
const fixtures = discoverFixtureFiles();

it("discovers at least one fixture per feature slice with a fixtures directory", () => {
expect(fixtures.length).toBeGreaterThan(0);
});

it.each(fixtures.map((f) => [f.relativePath, f] as const))(
"%s imports cleanly against the installed SDK",
async (_label, fixture) => {
await expect(
import(/* @vite-ignore */ fixture.absolutePath)
).resolves.toBeDefined();
}
);
});
56 changes: 56 additions & 0 deletions core/testing/fixtureRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { readdirSync, statSync } from "node:fs";
import path from "node:path";

const FEATURES_DIR = path.resolve(process.cwd(), "features");
const FIXTURE_DIR_NAME = "fixtures";
const FIXTURE_FILE_RE = /\.fixture\.tsx?$/;

export interface FixtureFileEntry {
/** Feature slice this fixture belongs to, e.g. "fee-stats". */
slice: string;
/** Absolute path to the fixture file. */
absolutePath: string;
/** Path relative to the repo root, used for reporting. */
relativePath: string;
}

/**
* Walks `features/*/fixtures/*.fixture.ts(x)` and returns every fixture
* file found, grouped by the slice that owns it.
*
* Fixtures are intentionally decentralized — one directory per slice, owned
* by that slice's contributor — so this discovers them by walking every
* feature directory rather than assuming a single shared fixtures root.
*/
export function discoverFixtureFiles(): FixtureFileEntry[] {
const entries: FixtureFileEntry[] = [];
let slices: string[];
try {
slices = readdirSync(FEATURES_DIR).filter((name) =>
statSync(path.join(FEATURES_DIR, name)).isDirectory()
);
} catch {
return entries;
}

for (const slice of slices) {
const fixturesDir = path.join(FEATURES_DIR, slice, FIXTURE_DIR_NAME);
let files: string[];
try {
files = readdirSync(fixturesDir);
} catch {
continue;
}
for (const file of files) {
if (!FIXTURE_FILE_RE.test(file)) continue;
const absolutePath = path.join(fixturesDir, file);
entries.push({
slice,
absolutePath,
relativePath: path.relative(process.cwd(), absolutePath)
});
}
}

return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
}
19 changes: 16 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,26 @@ module would test the mock instead of the code.
## Quality gates

```bash
npm run check # registry → lint → test → verify:features → build
npm run check # registry → lint → test → verify:features → verify:issues → verify:fixtures → build
```

CI runs the same steps on every pull request, including
`npm run verify:features`, which fails a slice that does not meet the contract,
and `npm run verify:issues`, which preserves a backlog of at least 40
independent specifications with a stable 20-issue advanced wave.
`npm run verify:issues`, which preserves a backlog of at least 40
independent specifications with a stable 20-issue advanced wave, and
`npm run verify:fixtures`, which imports every `features/*/fixtures/*.fixture.ts`
file and fails with the specific file if a `@stellar/stellar-sdk` upgrade
broke it.

### SDK upgrades

Fixtures are decentralized — one `fixtures/` directory per slice, owned by
that slice's contributor — so there is no single place to eyeball after
bumping `@stellar/stellar-sdk`. Most fixtures build their values with real SDK
calls (`Keypair`, `TransactionBuilder`, `xdr.*`) rather than hand-typing them,
so a renamed export or changed constructor throws the moment the fixture
module loads. Run `npm run verify:fixtures` as part of every SDK-upgrade PR —
it reports exactly which fixture file failed to import and why.

See [ISSUE_PUBLISHING.md](./ISSUE_PUBLISHING.md) for the five-at-a-time
GrantFox publication flow.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
"test:coverage": "vitest run --coverage",
"verify:features": "node scripts/verify-features.mjs",
"verify:issues": "node scripts/validate-issue-catalog.mjs",
"verify:fixtures": "vitest run core/testing/__tests__/fixtures.contract.test.ts --reporter=verbose",
"new:feature": "node scripts/new-feature.mjs",
"postinstall": "node scripts/generate-registry.mjs",
"check": "npm run lint && npm run test && npm run verify:features && npm run verify:issues && npm run build",
"check": "npm run lint && npm run test && npm run verify:features && npm run verify:issues && npm run verify:fixtures && npm run build",
"issues": "node scripts/create-issues.mjs",
"issues:drips": "node scripts/create-issues.mjs --target drips"
},
Expand Down
Loading