GitNexus/gitnexus/test/unit/group/fs-utils.test.ts
azizur100389 f64cc8b7a8
feat(group): add GraphQL cross-repo contracts (#3070)
* feat(group): add GraphQL contract extraction

* fix(group): tighten GraphQL contract guards

* fix(group): complete GraphQL review hardening

* fix(group): isolate bounded GraphQL reads

* fix(group): harden GraphQL contract extraction
2026-08-29 08:39:09 +01:00

89 lines
3.7 KiB
TypeScript

import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { readSafe, readSafeBounded } from '../../../src/core/group/extractors/fs-utils.js';
import { cleanupTempDir } from '../../helpers/test-db.js';
const tempDirs: string[] = [];
describe('group extractor readSafe', () => {
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => cleanupTempDir(dir)));
});
it('rejects a path whose canonical target escapes through a directory symlink', async () => {
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
const outside = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-outside-'));
tempDirs.push(repo, outside);
await fs.writeFile(path.join(outside, 'secret.graphql'), 'query Secret { secret }', 'utf8');
await fs.symlink(
outside,
path.join(repo, 'linked'),
process.platform === 'win32' ? 'junction' : 'dir',
);
await expect(readSafeBounded(repo, 'linked/secret.graphql', 1024)).resolves.toBeNull();
});
it('reads a regular file within the canonical repository root', async () => {
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
tempDirs.push(repo);
await fs.writeFile(path.join(repo, 'schema.graphql'), 'query Health { health }', 'utf8');
expect(readSafe(repo, 'schema.graphql')).toBe('query Health { health }');
await expect(readSafeBounded(repo, 'schema.graphql', 1024)).resolves.toBe(
'query Health { health }',
);
});
it('rejects an oversized sparse file before reading its contents', async () => {
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
tempDirs.push(repo);
const file = await fs.open(path.join(repo, 'oversized.graphql'), 'w');
try {
await file.truncate(16 * 1024 * 1024);
} finally {
await file.close();
}
await expect(readSafeBounded(repo, 'oversized.graphql', 1024)).resolves.toBeNull();
});
it('accepts a regular file whose size is exactly maxBytes', async () => {
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
tempDirs.push(repo);
await fs.writeFile(path.join(repo, 'exact.graphql'), '12345678', 'utf8');
await expect(readSafeBounded(repo, 'exact.graphql', 8)).resolves.toBe('12345678');
await expect(readSafeBounded(repo, 'exact.graphql', 7)).resolves.toBeNull();
});
it.skipIf(process.platform === 'win32')(
'reads a final-file symlink whose canonical target stays inside the repository',
async () => {
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
tempDirs.push(repo);
await fs.writeFile(path.join(repo, 'schema.graphql'), 'query Health { health }', 'utf8');
await fs.symlink('schema.graphql', path.join(repo, 'schema-link.graphql'), 'file');
await expect(readSafeBounded(repo, 'schema-link.graphql', 1024)).resolves.toBe(
'query Health { health }',
);
},
);
it.skipIf(process.platform === 'win32')(
'rejects a final-file symlink whose canonical target escapes the repository',
async () => {
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
const outside = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-outside-'));
tempDirs.push(repo, outside);
const secret = path.join(outside, 'secret.graphql');
await fs.writeFile(secret, 'query Secret { secret }', 'utf8');
await fs.symlink(secret, path.join(repo, 'secret-link.graphql'), 'file');
await expect(readSafeBounded(repo, 'secret-link.graphql', 1024)).resolves.toBeNull();
},
);
});