build-identity.test.ts102 lines · main
1/**
2 * Shared build-identity resolver tests — pin the env→git→"dev"
3 * fallback chain so every service (apps/api, apps/realtime,
4 * apps/runtime) gets the same answer for `/info.buildSha`.
5 */
6
7import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
8import { tmpdir } from 'node:os';
9import { join } from 'node:path';
10
11import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
12
13import { resolveBuildAtFromGit, resolveShaFromGit } from '@briven/shared';
14
15describe('resolveShaFromGit', () => {
16 let tmp: string;
17 let gitDir: string;
18 const SHA = 'a42bd57b3c1d8e2f4a6b7c9d0e1f2a3b4c5d6e7f';
19
20 beforeEach(() => {
21 tmp = mkdtempSync(join(tmpdir(), 'briven-git-'));
22 gitDir = join(tmp, '.git');
23 mkdirSync(gitDir, { recursive: true });
24 });
25
26 afterEach(() => {
27 rmSync(tmp, { recursive: true, force: true });
28 });
29
30 test('returns null when .git/HEAD is missing (dev tarball case)', () => {
31 expect(resolveShaFromGit(gitDir)).toBeNull();
32 });
33
34 test('resolves a detached HEAD (sha written directly)', () => {
35 writeFileSync(join(gitDir, 'HEAD'), SHA + '\n');
36 expect(resolveShaFromGit(gitDir)).toBe(SHA);
37 });
38
39 test('resolves a loose ref (refs/heads/main)', () => {
40 writeFileSync(join(gitDir, 'HEAD'), 'ref: refs/heads/main\n');
41 mkdirSync(join(gitDir, 'refs/heads'), { recursive: true });
42 writeFileSync(join(gitDir, 'refs/heads/main'), SHA + '\n');
43 expect(resolveShaFromGit(gitDir)).toBe(SHA);
44 });
45
46 test('falls back to packed-refs when loose ref is absent', () => {
47 writeFileSync(join(gitDir, 'HEAD'), 'ref: refs/heads/main\n');
48 writeFileSync(
49 join(gitDir, 'packed-refs'),
50 `# pack-refs with: peeled fully-peeled sorted\n${SHA} refs/heads/main\n`,
51 );
52 expect(resolveShaFromGit(gitDir)).toBe(SHA);
53 });
54
55 test('skips comment + peeled lines in packed-refs', () => {
56 writeFileSync(join(gitDir, 'HEAD'), 'ref: refs/heads/main\n');
57 writeFileSync(
58 join(gitDir, 'packed-refs'),
59 [
60 '# pack-refs with: peeled fully-peeled sorted',
61 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef refs/heads/other',
62 `${SHA} refs/heads/main`,
63 '^cafebabecafebabecafebabecafebabecafebabe',
64 ].join('\n') + '\n',
65 );
66 expect(resolveShaFromGit(gitDir)).toBe(SHA);
67 });
68
69 test('returns null on malformed sha (corrupted ref)', () => {
70 writeFileSync(join(gitDir, 'HEAD'), 'ref: refs/heads/main\n');
71 mkdirSync(join(gitDir, 'refs/heads'), { recursive: true });
72 writeFileSync(join(gitDir, 'refs/heads/main'), 'not-a-sha\n');
73 expect(resolveShaFromGit(gitDir)).toBeNull();
74 });
75});
76
77describe('resolveBuildAtFromGit', () => {
78 let tmp: string;
79 let gitDir: string;
80
81 beforeEach(() => {
82 tmp = mkdtempSync(join(tmpdir(), 'briven-git-at-'));
83 gitDir = join(tmp, '.git');
84 mkdirSync(gitDir, { recursive: true });
85 });
86
87 afterEach(() => {
88 rmSync(tmp, { recursive: true, force: true });
89 });
90
91 test('returns ISO timestamp derived from HEAD mtime', () => {
92 writeFileSync(join(gitDir, 'HEAD'), 'ref: refs/heads/main\n');
93 const out = resolveBuildAtFromGit(gitDir);
94 expect(out).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
95 const delta = Math.abs(Date.now() - new Date(out as string).getTime());
96 expect(delta).toBeLessThan(5_000);
97 });
98
99 test('returns null when HEAD is absent', () => {
100 expect(resolveBuildAtFromGit(gitDir)).toBeNull();
101 });
102});