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
6 changes: 3 additions & 3 deletions packages/grpc/src/__tests__/glue-action-refresh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import { afterAll, afterEach, beforeAll, describe, expect, it, mock } from 'bun:test';
import { mkdirSync, symlinkSync, writeFileSync } from 'node:fs';
import { mkdirSync, writeFileSync } from 'node:fs';
import { createServer, type Server } from 'node:http';
import type { AddressInfo } from 'node:net';
import { dirname, join } from 'node:path';
Expand All @@ -29,7 +29,7 @@ import {
} from '../../fixtures/zone/src/generated/grpc/orders/v1/orders_pb';
import { generateFiles } from '../generate';
import * as runtime from '../runtime';
import { emptyDir, FIXTURE_ZONE, PKG_DIR, removeScratchDirs } from './helpers';
import { emptyDir, FIXTURE_ZONE, linkWorkspaceRuntime, removeScratchDirs } from './helpers';

const TAG = 'knext:orders:orders.v1.OrdersService';
const AUTHORIZE = Symbol.for('knext.test.glueActionRefresh.authorize');
Expand Down Expand Up @@ -98,7 +98,7 @@ beforeAll(async () => {
}
`,
);
symlinkSync(join(PKG_DIR, 'node_modules'), join(dir, 'node_modules'));
linkWorkspaceRuntime(dir);
actions = await import(
join(dir, 'src', 'generated', 'grpc', 'orders', 'v1', 'orders_knext_actions.ts')
);
Expand Down
43 changes: 43 additions & 0 deletions packages/grpc/src/__tests__/glue-runtime-link-guard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Guard: a spec that mocks `@getknext/grpc/runtime` AND imports generated glue
* from a scratch dir must make that specifier resolve with
* `linkWorkspaceRuntime`. A bare `mock.module` passes only while
* `packages/grpc/dist` is absent (Bun keys the mock to the dist path once it
* exists), so the spec's result would depend on which test built dist first.
* Scans the specs, so a new one cannot forget the link.
*/

import { describe, expect, it } from 'bun:test';
import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

const DIR = import.meta.dir;
const MOCKS_RUNTIME = /mock\.module\(\s*['"]@getknext\/grpc\/runtime['"]/;
const SCRATCH = /\b(emptyDir|copyFixtureZone|mkdtempSync)\(/;

function violations(files: Record<string, string>): string[] {
return Object.entries(files)
.filter(
([, src]) =>
MOCKS_RUNTIME.test(src) && SCRATCH.test(src) && !/\blinkWorkspaceRuntime\(/.test(src),
)
.map(([name]) => name);
}

describe('scratch glue specs link the workspace runtime', () => {
it('flags a spec that mocks the runtime in a scratch dir without the link', () => {
const bad = `mock.module('@getknext/grpc/runtime', () => r);\nconst d = emptyDir('x');`;
expect(violations({ 'bad.test.ts': bad })).toEqual(['bad.test.ts']);
expect(violations({ 'ok.test.ts': `${bad}\nlinkWorkspaceRuntime(d);` })).toEqual([]);
});

it('every spec in this directory obeys it', () => {
const files: Record<string, string> = {};
for (const f of readdirSync(DIR).filter((n) => n.endsWith('.test.ts'))) {
if (f === 'glue-runtime-link-guard.test.ts') continue;
files[f] = readFileSync(join(DIR, f), 'utf8');
}
expect(Object.keys(files).length).toBeGreaterThan(10);
expect(violations(files)).toEqual([]);
});
});
42 changes: 41 additions & 1 deletion packages/grpc/src/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@
* file calls {@link removeScratchDirs} from its `afterAll`.
*/

import { cpSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
import {
cpSync,
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
statSync,
symlinkSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join, relative, resolve } from 'node:path';

Expand All @@ -27,6 +37,36 @@ export function removeScratchDirs(): void {
scratchDirs.length = 0;
}

/**
* Make `@getknext/grpc/runtime` RESOLVE from a scratch zone outside the repo.
* `mock.module` alone is order-dependent: Bun keys the mock under the bare name
* only while `packages/grpc/dist` is absent; once a build has left dist on disk
* it keys the mock to the dist path and a bare import from /tmp misses it.
*/
export function linkWorkspaceRuntime(dir: string): void {
const modules = join(dir, 'node_modules');
mkdirSync(modules, { recursive: true });
const deps = join(PKG_DIR, 'node_modules');
for (const entry of readdirSync(deps)) {
if (entry === '.bin' || entry === '@getknext') continue;
symlinkSync(join(deps, entry), join(modules, entry));
}
const stub = join(modules, '@getknext', 'grpc');
mkdirSync(stub, { recursive: true });
writeFileSync(
join(stub, 'package.json'),
JSON.stringify({
name: '@getknext/grpc',
type: 'module',
exports: { './runtime': './runtime.ts' },
}),
);
writeFileSync(
join(stub, 'runtime.ts'),
`export * from ${JSON.stringify(join(PKG_DIR, 'src', 'runtime'))};\n`,
);
}

/** A fresh copy of the fixture zone (proto + config + committed output). */
export function copyFixtureZone(prefix: string): string {
const dir = emptyDir(prefix);
Expand Down
9 changes: 7 additions & 2 deletions packages/grpc/src/__tests__/zone-signer-glue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { CatalogService } from '../../fixtures/zone/src/generated/grpc/orders/v1
import { OrdersService } from '../../fixtures/zone/src/generated/grpc/orders/v1/orders_pb';
import { generateFiles } from '../generate';
import * as runtime from '../runtime';
import { emptyDir, FIXTURE_ZONE, PKG_DIR, removeScratchDirs } from './helpers';
import { emptyDir, FIXTURE_ZONE, linkWorkspaceRuntime, removeScratchDirs } from './helpers';

const ISSUER = 'knext:zone:shop/storefront';
const AUDIENCE = 'knext:fn:shop/orders';
Expand Down Expand Up @@ -168,7 +168,12 @@ beforeAll(async () => {
}
`,
);
symlinkSync(join(PKG_DIR, 'node_modules'), join(dir, 'node_modules'));
// The glue imports `@getknext/grpc/runtime`. `mock.module` alone is not enough
// from a scratch dir outside the repo: Bun keys the mock under the bare name
// only while `packages/grpc/dist` is absent; once any earlier test has built
// dist it keys the mock to the dist path and the bare import misses it. Link
// the workspace runtime so the import resolves for real either way.
linkWorkspaceRuntime(dir);
const out = join(dir, 'src', 'generated', 'grpc', 'orders', 'v1');
server_ = await import(join(out, 'orders_knext_server.ts'));
actions = await import(join(out, 'orders_knext_actions.ts'));
Expand Down
Loading