Skip to content

Commit 72a71ca

Browse files
committed
fix(ci): make runtime path tests cross-platform
1 parent cab489d commit 72a71ca

2 files changed

Lines changed: 86 additions & 53 deletions

File tree

server/src/runtime/paths.test.ts

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,85 @@
11
import assert from 'node:assert/strict';
2+
import { posix, win32 } from 'node:path';
23
import test from 'node:test';
34
import { resolveDataDirForTest } from './paths.js';
45

5-
test('resolveDataDirForTest uses absolute DATA_DIR verbatim', () => {
6-
const dataDir = resolveDataDirForTest({
7-
envDataDir: 'C:/data/chatcrystal',
8-
packageRoot: 'C:/pkg/server',
9-
workspaceRoot: 'C:/pkg',
10-
homeDir: 'C:/Users/Rayner',
11-
});
12-
13-
assert.equal(dataDir, 'C:/data/chatcrystal');
14-
});
15-
16-
test('resolveDataDirForTest resolves relative DATA_DIR against workspace root', () => {
17-
const dataDir = resolveDataDirForTest({
18-
envDataDir: './data',
6+
const pathCases = [
7+
{
8+
name: 'Windows',
9+
pathResolver: win32,
10+
absoluteDataDir: 'C:/data/chatcrystal',
1911
packageRoot: 'C:/repo/server',
2012
workspaceRoot: 'C:/repo',
13+
globalPackageRoot: 'C:/global/node_modules/chatcrystal',
2114
homeDir: 'C:/Users/Rayner',
15+
},
16+
{
17+
name: 'POSIX',
18+
pathResolver: posix,
19+
absoluteDataDir: '/data/chatcrystal',
20+
packageRoot: '/repo/server',
21+
workspaceRoot: '/repo',
22+
globalPackageRoot: '/global/node_modules/chatcrystal',
23+
homeDir: '/home/rayner',
24+
},
25+
];
26+
27+
for (const pathCase of pathCases) {
28+
test(`resolveDataDirForTest uses absolute DATA_DIR verbatim on ${pathCase.name}`, () => {
29+
const dataDir = resolveDataDirForTest({
30+
envDataDir: pathCase.absoluteDataDir,
31+
packageRoot: pathCase.packageRoot,
32+
workspaceRoot: pathCase.workspaceRoot,
33+
homeDir: pathCase.homeDir,
34+
pathResolver: pathCase.pathResolver,
35+
});
36+
37+
assert.equal(dataDir, pathCase.absoluteDataDir);
2238
});
2339

24-
assert.equal(dataDir, 'C:\\repo\\data');
25-
});
40+
test(`resolveDataDirForTest resolves relative DATA_DIR against workspace root on ${pathCase.name}`, () => {
41+
const dataDir = resolveDataDirForTest({
42+
envDataDir: './data',
43+
packageRoot: pathCase.packageRoot,
44+
workspaceRoot: pathCase.workspaceRoot,
45+
homeDir: pathCase.homeDir,
46+
pathResolver: pathCase.pathResolver,
47+
});
2648

27-
test('resolveDataDirForTest falls back to package root when no workspace root exists', () => {
28-
const dataDir = resolveDataDirForTest({
29-
envDataDir: './data',
30-
packageRoot: 'C:/global/node_modules/chatcrystal',
31-
workspaceRoot: null,
32-
homeDir: 'C:/Users/Rayner',
49+
assert.equal(dataDir, pathCase.pathResolver.resolve(pathCase.workspaceRoot, 'data'));
3350
});
3451

35-
assert.equal(dataDir, 'C:\\global\\node_modules\\chatcrystal\\data');
36-
});
52+
test(`resolveDataDirForTest falls back to package root when no workspace root exists on ${pathCase.name}`, () => {
53+
const dataDir = resolveDataDirForTest({
54+
envDataDir: './data',
55+
packageRoot: pathCase.globalPackageRoot,
56+
workspaceRoot: null,
57+
homeDir: pathCase.homeDir,
58+
pathResolver: pathCase.pathResolver,
59+
});
3760

38-
test('resolveDataDirForTest defaults to ~/.chatcrystal/data when DATA_DIR is unset in a repo checkout', () => {
39-
const dataDir = resolveDataDirForTest({
40-
packageRoot: 'C:/repo/server',
41-
workspaceRoot: 'C:/repo',
42-
homeDir: 'C:/Users/Rayner',
61+
assert.equal(dataDir, pathCase.pathResolver.resolve(pathCase.globalPackageRoot, 'data'));
4362
});
4463

45-
assert.equal(dataDir, 'C:\\Users\\Rayner\\.chatcrystal\\data');
46-
});
64+
test(`resolveDataDirForTest defaults to ~/.chatcrystal/data when DATA_DIR is unset in a repo checkout on ${pathCase.name}`, () => {
65+
const dataDir = resolveDataDirForTest({
66+
packageRoot: pathCase.packageRoot,
67+
workspaceRoot: pathCase.workspaceRoot,
68+
homeDir: pathCase.homeDir,
69+
pathResolver: pathCase.pathResolver,
70+
});
4771

48-
test('resolveDataDirForTest defaults to ~/.chatcrystal/data when installed outside a workspace', () => {
49-
const dataDir = resolveDataDirForTest({
50-
packageRoot: 'C:/Users/Rayner/AppData/Roaming/npm/node_modules/chatcrystal',
51-
workspaceRoot: null,
52-
homeDir: 'C:/Users/Rayner',
72+
assert.equal(dataDir, pathCase.pathResolver.resolve(pathCase.homeDir, '.chatcrystal', 'data'));
5373
});
5474

55-
assert.equal(dataDir, 'C:\\Users\\Rayner\\.chatcrystal\\data');
56-
});
75+
test(`resolveDataDirForTest defaults to ~/.chatcrystal/data when installed outside a workspace on ${pathCase.name}`, () => {
76+
const dataDir = resolveDataDirForTest({
77+
packageRoot: pathCase.globalPackageRoot,
78+
workspaceRoot: null,
79+
homeDir: pathCase.homeDir,
80+
pathResolver: pathCase.pathResolver,
81+
});
82+
83+
assert.equal(dataDir, pathCase.pathResolver.resolve(pathCase.homeDir, '.chatcrystal', 'data'));
84+
});
85+
}

server/src/runtime/paths.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { existsSync, readFileSync } from 'node:fs';
22
import { homedir } from 'node:os';
3-
import { dirname, isAbsolute, resolve } from 'node:path';
3+
import path from 'node:path';
44
import { config } from 'dotenv';
55

66
type PackageJson = {
77
workspaces?: unknown;
88
};
99

10+
type PathResolver = Pick<typeof path, 'isAbsolute' | 'resolve'>;
11+
1012
type RuntimePathOptions = {
1113
envDataDir?: string;
1214
packageRoot: string;
1315
workspaceRoot?: string | null;
1416
homeDir?: string;
17+
pathResolver?: PathResolver;
1518
};
1619

1720
function readPackageJson(dir: string): PackageJson | null {
18-
const manifestPath = resolve(dir, 'package.json');
21+
const manifestPath = path.resolve(dir, 'package.json');
1922
if (!existsSync(manifestPath)) {
2023
return null;
2124
}
@@ -28,31 +31,31 @@ function readPackageJson(dir: string): PackageJson | null {
2831
}
2932

3033
function findNearestPackageRoot(startDir: string): string {
31-
let current = resolve(startDir);
34+
let current = path.resolve(startDir);
3235

3336
while (true) {
3437
if (readPackageJson(current)) {
3538
return current;
3639
}
3740

38-
const parent = dirname(current);
41+
const parent = path.dirname(current);
3942
if (parent === current) {
40-
return resolve(startDir, '../../..');
43+
return path.resolve(startDir, '../../..');
4144
}
4245
current = parent;
4346
}
4447
}
4548

4649
function findWorkspaceRoot(packageRoot: string): string | null {
47-
let current = dirname(packageRoot);
50+
let current = path.dirname(packageRoot);
4851

4952
while (true) {
5053
const manifest = readPackageJson(current);
5154
if (manifest?.workspaces) {
5255
return current;
5356
}
5457

55-
const parent = dirname(current);
58+
const parent = path.dirname(current);
5659
if (parent === current) {
5760
return null;
5861
}
@@ -67,23 +70,24 @@ function unique<T>(items: T[]): T[] {
6770
export function resolveDataDirForTest(options: RuntimePathOptions): string {
6871
const appRoot = options.workspaceRoot ?? options.packageRoot;
6972
const homeDir = options.homeDir ?? homedir();
73+
const pathResolver = options.pathResolver ?? path;
7074

71-
if (options.envDataDir && isAbsolute(options.envDataDir)) {
75+
if (options.envDataDir && pathResolver.isAbsolute(options.envDataDir)) {
7276
return options.envDataDir;
7377
}
7478

7579
if (options.envDataDir) {
76-
return resolve(appRoot, options.envDataDir);
80+
return pathResolver.resolve(appRoot, options.envDataDir);
7781
}
7882

79-
return resolve(homeDir, '.chatcrystal', 'data');
83+
return pathResolver.resolve(homeDir, '.chatcrystal', 'data');
8084
}
8185

8286
const packageRoot = findNearestPackageRoot(import.meta.dirname);
8387
const workspaceRoot = findWorkspaceRoot(packageRoot);
8488
const appRoot = workspaceRoot ?? packageRoot;
8589

86-
for (const envPath of unique([resolve(appRoot, '.env'), resolve(packageRoot, '.env')])) {
90+
for (const envPath of unique([path.resolve(appRoot, '.env'), path.resolve(packageRoot, '.env')])) {
8791
if (existsSync(envPath)) {
8892
config({ path: envPath });
8993
break;
@@ -101,8 +105,8 @@ export const runtimePaths = {
101105
packageRoot,
102106
workspaceRoot,
103107
dataDir,
104-
dbPath: resolve(dataDir, 'chatcrystal.db'),
105-
configPath: resolve(dataDir, 'config.json'),
106-
pidPath: resolve(dataDir, 'crystal.pid'),
107-
logPath: resolve(dataDir, 'crystal-server.log'),
108+
dbPath: path.resolve(dataDir, 'chatcrystal.db'),
109+
configPath: path.resolve(dataDir, 'config.json'),
110+
pidPath: path.resolve(dataDir, 'crystal.pid'),
111+
logPath: path.resolve(dataDir, 'crystal-server.log'),
108112
};

0 commit comments

Comments
 (0)