chore: refactor unit tests

This commit is contained in:
Aarnav Tale
2025-11-18 02:11:53 -05:00
parent afd512138b
commit 3d9b6370bf
6 changed files with 161 additions and 115 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
run: ./build.sh --skip-pnpm-prune run: ./build.sh --skip-pnpm-prune
- name: Run unit tests - name: Run unit tests
run: pnpm test run: pnpm run test:unit
- name: Run integration tests - name: Run integration tests
run: pnpm run test:integration run: pnpm run test:integration
+2 -3
View File
@@ -10,8 +10,8 @@
"dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev", "dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev",
"start": "node build/server/index.js", "start": "node build/server/index.js",
"typecheck": "react-router typegen && tsgo", "typecheck": "react-router typegen && tsgo",
"test": "vitest run", "test:unit": "vitest run --project unit",
"test:integration": "vitest run --mode integration", "test:integration": "vitest run --project integration",
"docs:dev": "vitepress dev docs", "docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs", "docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs", "docs:preview": "vitepress preview docs",
@@ -56,7 +56,6 @@
"drizzle-orm": "0.44.7", "drizzle-orm": "0.44.7",
"isbot": "5.1.32", "isbot": "5.1.32",
"jose": "6.1.2", "jose": "6.1.2",
"js-yaml": "^4.1.1",
"lefthook": "^2.0.4", "lefthook": "^2.0.4",
"lucide-react": "^0.540.0", "lucide-react": "^0.540.0",
"mime": "^4.1.0", "mime": "^4.1.0",
-3
View File
@@ -122,9 +122,6 @@ importers:
jose: jose:
specifier: 6.1.2 specifier: 6.1.2
version: 6.1.2 version: 6.1.2
js-yaml:
specifier: ^4.1.1
version: 4.1.1
lefthook: lefthook:
specifier: ^2.0.4 specifier: ^2.0.4
version: 2.0.4 version: 2.0.4
@@ -1,48 +1,50 @@
import fs from 'node:fs'; import { mkdtemp, writeFile } from 'node:fs/promises';
import os from 'node:os'; import { tmpdir } from 'node:os';
import path from 'node:path'; import { join } from 'node:path';
import { fileURLToPath } from 'node:url'; import { afterAll, beforeEach, describe, expect, it } from 'vitest';
import yaml from 'js-yaml'; import { stringify } from 'yaml';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { ConfigError, loadConfig } from '~/server/config/loader';
import { overlayFS } from './setupOverlayFs.js'; import { HeadplaneConfig } from '~/server/config/schema';
import { clearFakeFiles, createFakeFile } from './setup/overlay-fs';
// Get the directory name in ESM async function writeTempFile(baseName: string, content: string) {
const __filename = fileURLToPath(import.meta.url); const dir = await mkdtemp(join(tmpdir(), 'headplane-test-'));
const __dirname = path.dirname(__filename);
// Helper to create a temp file with given content, return its path const path = join(dir, baseName);
function writeTempFile(baseName, content) { await writeFile(path, content);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'headplane-test-')); return path;
const filePath = path.join(dir, baseName);
fs.writeFileSync(filePath, content);
return filePath;
} }
// Helper to create a temporary YAML config file with minimal required structure type RecursivePartial<T> = {
function writeTempYamlConfig(customConfig = {}) { [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
};
function writeTempYamlConfig(
customConfig: RecursivePartial<HeadplaneConfig> | string = {},
) {
const defaultConfig = { const defaultConfig = {
debug: false, debug: false,
server: { server: {
host: '127.0.0.1', host: '127.0.0.1',
port: 8080, port: 8080,
data_path: '/var/lib/headplane',
cookie_secret: '12345678901234567890123456789012', cookie_secret: '12345678901234567890123456789012',
cookie_secure: false, cookie_secure: false,
agent: { cookie_max_age: 86400,
authkey: 'testagentauthkey',
ttl: 180000,
cache_path: '/tmp/agent_cache.json',
},
}, },
headscale: { url: 'http://localhost:8081', config_strict: false }, headscale: { url: 'http://localhost:8081', config_strict: false },
}; } satisfies HeadplaneConfig;
function deepMerge(target, source) { // biome-ignore lint/suspicious/noExplicitAny: I don't care
function deepMerge(target: any, source: any): any {
let output = { ...target }; let output = { ...target };
if (isObject(target) && isObject(source)) { if (isObject(target) && isObject(source)) {
output = { ...target, ...source }; output = { ...target, ...source };
for (const key of Object.keys(source)) { for (const key of Object.keys(source)) {
if (isObject(source[key]) && key in target && isObject(target[key])) { const sourceValue = source[key as keyof typeof source];
output[key] = deepMerge(target[key], source[key]); const targetValue = target[key as keyof typeof target];
if (isObject(sourceValue) && key in target && isObject(targetValue)) {
output[key] = deepMerge(targetValue, sourceValue);
} else { } else {
output[key] = source[key]; output[key] = source[key];
} }
@@ -73,13 +75,14 @@ function writeTempYamlConfig(customConfig = {}) {
return output; return output;
} }
function isObject(item) { // biome-ignore lint/suspicious/noExplicitAny: I don't care
function isObject(item: any): item is Record<string, unknown> {
return item && typeof item === 'object' && !Array.isArray(item); return item && typeof item === 'object' && !Array.isArray(item);
} }
const merged = deepMerge(defaultConfig, customConfig); const merged = deepMerge(defaultConfig, customConfig);
const yamlContent = const yamlContent =
typeof customConfig === 'string' ? customConfig : yaml.dump(merged); typeof customConfig === 'string' ? customConfig : stringify(merged);
return writeTempFile('config.yaml', yamlContent); return writeTempFile('config.yaml', yamlContent);
} }
@@ -87,60 +90,43 @@ function writeTempYamlConfig(customConfig = {}) {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
describe('Configuration Loading', () => { describe('Configuration Loading', () => {
let loadConfig;
let ConfigError;
beforeAll(async () => {
const module = await import('../app/server/config/loader.js');
loadConfig = module.loadConfig;
ConfigError = module.ConfigError;
});
beforeEach(() => { beforeEach(() => {
// biome-ignore lint/performance/noDelete: needed for test cleanup
delete process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH; delete process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH;
// biome-ignore lint/performance/noDelete: needed for test cleanup
delete process.env.HEADPLANE_SERVER__COOKIE_SECRET_PATH; delete process.env.HEADPLANE_SERVER__COOKIE_SECRET_PATH;
// biome-ignore lint/performance/noDelete: needed for test cleanup
delete process.env.HEADPLANE_HEADSCALE__API_KEY_PATH; delete process.env.HEADPLANE_HEADSCALE__API_KEY_PATH;
// biome-ignore lint/performance/noDelete: needed for test cleanup
delete process.env.TEST_SECRET_DIR; delete process.env.TEST_SECRET_DIR;
// Clear overlayfs clearFakeFiles();
overlayFS.clear(); createFakeFile('/var/lib/headplane/agent_cache.json', JSON.stringify({}));
createFakeFile(
// Create default files that the config expects
overlayFS.createFile(
'/var/lib/headplane/agent_cache.json',
JSON.stringify({}),
);
overlayFS.createFile(
'/var/lib/headplane/users.json', '/var/lib/headplane/users.json',
`[{"u":"acb3294f89a16b554e06b80d5266a3c8b09a883e1fa78ac459a550bf52a32564","c":65535}]`, `[{"u":"acb3294f89a16b554e06b80d5266a3c8b09a883e1fa78ac459a550bf52a32564","c":65535}]`,
); );
overlayFS.createFile('/tmp/agent_cache.json', JSON.stringify({})); createFakeFile('/tmp/agent_cache.json', JSON.stringify({}));
createFakeFile('irrelevant', 'irrelevant-content');
// Create test files that some tests expect to exist createFakeFile('placeholder', 'placeholder-content');
overlayFS.createFile('irrelevant', 'irrelevant-content');
overlayFS.createFile('placeholder', 'placeholder-content');
}); });
afterAll(() => { afterAll(() => {
Object.assign(process.env, originalEnv); Object.assign(process.env, originalEnv);
overlayFS.clear(); clearFakeFiles();
}); });
describe('OIDC Configuration', () => { describe('OIDC Configuration', () => {
const minimalOidcFields = { const minimalOidcFields = {
token_endpoint_auth_method: 'client_secret_basic', token_endpoint_auth_method: 'client_secret_basic' as const,
disable_api_key_login: false, disable_api_key_login: false,
headscale_api_key: 'dummyKey', headscale_api_key: 'dummyKey',
user_storage_file: '/var/lib/headplane/users.json',
profile_picture_source: 'oidc' as const,
strict_validation: true,
scope: 'openid email profile',
}; };
it('should load client_secret from file specified in client_secret_path', async () => { it('should load client_secret from file specified in client_secret_path', async () => {
const secretValue = 'yaml-file-oidc-secret'; const secretValue = 'yaml-file-oidc-secret';
const secretPath = writeTempFile('oidc_secret.txt', secretValue); const secretPath = await writeTempFile('oidc_secret.txt', secretValue);
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
@@ -149,19 +135,19 @@ describe('Configuration Loading', () => {
}, },
}); });
const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); const config = await loadConfig({ loadEnv: false, path: tempConfigPath });
expect(config.oidc.client_secret).toBe(secretValue); expect(config.oidc?.client_secret).toBe(secretValue);
}); });
it('should override YAML client_secret_path with environment variable', async () => { it('should override YAML client_secret_path with environment variable', async () => {
const envValue = 'env-file-oidc-secret'; const envValue = 'env-file-oidc-secret';
const envPath = writeTempFile('env_oidc_secret.txt', envValue); const envPath = await writeTempFile('env_oidc_secret.txt', envValue);
process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = envPath; process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = envPath;
// Instead of 'irrelevant', use a temp file that exists // Instead of 'irrelevant', use a temp file that exists
const irrelevantPath = writeTempFile( const irrelevantPath = await writeTempFile(
'irrelevant.txt', 'irrelevant.txt',
'irrelevant-content', 'irrelevant-content',
); );
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
@@ -170,25 +156,25 @@ describe('Configuration Loading', () => {
}, },
}); });
const config = await loadConfig({ loadEnv: true, path: tempConfigPath }); const config = await loadConfig({ loadEnv: true, path: tempConfigPath });
expect(config.oidc.client_secret).toBe(envValue); expect(config.oidc?.client_secret).toBe(envValue);
}); });
it('should handle environment variable interpolation in client_secret_path', async () => { it('should handle environment variable interpolation in client_secret_path', async () => {
const value = 'interpolated-secret'; const value = 'interpolated-secret';
const dir = fs.mkdtempSync( const dir = await mkdtemp(join(tmpdir(), 'headplane-secret-dir-'));
path.join(os.tmpdir(), 'headplane-secret-dir-'), const filePath = join(dir, 'secret.txt');
); await writeFile(filePath, value);
const file = path.join(dir, 'secret.txt');
fs.writeFileSync(file, value);
process.env.TEST_SECRET_DIR = dir; process.env.TEST_SECRET_DIR = dir;
process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH =
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
'${TEST_SECRET_DIR}/secret.txt'; '${TEST_SECRET_DIR}/secret.txt';
// Instead of 'placeholder', use a temp file that exists // Instead of 'placeholder', use a temp file that exists
const placeholderPath = writeTempFile( const placeholderPath = await writeTempFile(
'placeholder.txt', 'placeholder.txt',
'placeholder-content', 'placeholder-content',
); );
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
@@ -197,11 +183,11 @@ describe('Configuration Loading', () => {
}, },
}); });
const config = await loadConfig({ loadEnv: true, path: tempConfigPath }); const config = await loadConfig({ loadEnv: true, path: tempConfigPath });
expect(config.oidc.client_secret).toBe(value); expect(config.oidc?.client_secret).toBe(value);
}); });
it('should reject when client_secret_path points to non-existent file', async () => { it('should reject when client_secret_path points to non-existent file', async () => {
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
@@ -216,8 +202,9 @@ describe('Configuration Loading', () => {
it('should reject when client_secret_path has unresolvable env var interpolation', async () => { it('should reject when client_secret_path has unresolvable env var interpolation', async () => {
process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH =
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
'${MISSING_DIR}/secret.txt'; '${MISSING_DIR}/secret.txt';
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
@@ -234,7 +221,7 @@ describe('Configuration Loading', () => {
describe('Server Configuration', () => { describe('Server Configuration', () => {
it('should load cookie_secret directly from YAML', async () => { it('should load cookie_secret directly from YAML', async () => {
const valid = 'abcdefghijklmnopqrstuvwxyz123456'; const valid = 'abcdefghijklmnopqrstuvwxyz123456';
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
server: { cookie_secret: valid }, server: { cookie_secret: valid },
}); });
const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); const config = await loadConfig({ loadEnv: false, path: tempConfigPath });
@@ -243,8 +230,8 @@ describe('Configuration Loading', () => {
it('should load cookie_secret from file', async () => { it('should load cookie_secret from file', async () => {
const secret = 'a'.repeat(32); const secret = 'a'.repeat(32);
const secretPath = writeTempFile('cookie_secret.txt', secret); const secretPath = await writeTempFile('cookie_secret.txt', secret);
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
server: { cookie_secret_path: secretPath }, server: { cookie_secret_path: secretPath },
}); });
const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); const config = await loadConfig({ loadEnv: false, path: tempConfigPath });
@@ -252,8 +239,8 @@ describe('Configuration Loading', () => {
}); });
it('should reject when both cookie_secret and cookie_secret_path are in YAML', async () => { it('should reject when both cookie_secret and cookie_secret_path are in YAML', async () => {
const secretPath = writeTempFile('conflict.txt', 'x'.repeat(32)); const secretPath = await writeTempFile('conflict.txt', 'x'.repeat(32));
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
server: { server: {
cookie_secret: '1'.repeat(32), cookie_secret: '1'.repeat(32),
cookie_secret_path: secretPath, cookie_secret_path: secretPath,
@@ -262,67 +249,76 @@ describe('Configuration Loading', () => {
await expect( await expect(
loadConfig({ loadEnv: false, path: tempConfigPath }), loadConfig({ loadEnv: false, path: tempConfigPath }),
).rejects.toThrow( ).rejects.toThrow(
/Only one of \"cookie_secret\" or \"cookie_secret_path\" may be set/, /Only one of "cookie_secret" or "cookie_secret_path" may be set/,
); );
}); });
it('should reject when neither cookie_secret nor cookie_secret_path is provided', async () => { it('should reject when neither cookie_secret nor cookie_secret_path is provided', async () => {
const yaml = `debug: false const yaml = `
debug: false
server: server:
host: \"127.0.0.1\" host: "127.0.0.1"
port: 8080 port: 8080
cookie_secure: false cookie_secure: false
agent: agent:
authkey: \"key\" authkey: "key"
ttl: 180000 ttl: 180000
cache_path: \"/tmp/cache.json\" cache_path: "/tmp/cache.json"
headscale: headscale:
url: \"http://localhost\" url: "http://localhost"
config_strict: false config_strict: false
`; `;
const tempConfigPath = writeTempYamlConfig(yaml); const tempConfigPath = await writeTempYamlConfig(yaml);
await expect( await expect(
loadConfig({ loadEnv: false, path: tempConfigPath }), loadConfig({ loadEnv: false, path: tempConfigPath }),
).rejects.toThrow( ).rejects.toThrow(
/Either \"cookie_secret\" or \"cookie_secret_path\" must be provided for cookie_secret/, /Either "cookie_secret" or "cookie_secret_path" must be provided for cookie_secret/,
); );
}); });
}); });
describe('Headscale Configuration', () => { describe('Headscale Configuration', () => {
it('should load headscale_api_key directly from YAML', async () => { it('should load headscale_api_key directly from YAML', async () => {
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
headscale_api_key: 'hs-yaml-key', headscale_api_key: 'hs-yaml-key',
token_endpoint_auth_method: 'client_secret_basic', token_endpoint_auth_method: 'client_secret_basic',
disable_api_key_login: false, disable_api_key_login: false,
user_storage_file: '/var/lib/headplane/users.json',
profile_picture_source: 'oidc',
strict_validation: true,
scope: 'openid email profile',
}, },
}); });
const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); const config = await loadConfig({ loadEnv: false, path: tempConfigPath });
expect(config.oidc.headscale_api_key).toBe('hs-yaml-key'); expect(config.oidc?.headscale_api_key).toBe('hs-yaml-key');
}); });
it('should load headscale_api_key from file', async () => { it('should load headscale_api_key from file', async () => {
const val = 'hs-file-key'; const val = 'hs-file-key';
const p = writeTempFile('hs_api_key.txt', val); const p = await writeTempFile('hs_api_key.txt', val);
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
headscale_api_key_path: p, headscale_api_key_path: p,
token_endpoint_auth_method: 'client_secret_basic', token_endpoint_auth_method: 'client_secret_basic',
disable_api_key_login: false, disable_api_key_login: false,
user_storage_file: '/var/lib/headplane/users.json',
profile_picture_source: 'oidc',
strict_validation: true,
scope: 'openid email profile',
}, },
}); });
const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); const config = await loadConfig({ loadEnv: false, path: tempConfigPath });
expect(config.oidc.headscale_api_key).toBe(val); expect(config.oidc?.headscale_api_key).toBe(val);
}); });
it('should reject when both headscale_api_key and headscale_api_key_path are in YAML', async () => { it('should reject when both headscale_api_key and headscale_api_key_path are in YAML', async () => {
const p = writeTempFile('conflict.txt', 'irrelevant'); const p = await writeTempFile('conflict.txt', 'irrelevant');
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
oidc: { oidc: {
issuer: 'https://example.com/oidc', issuer: 'https://example.com/oidc',
client_id: 'test', client_id: 'test',
@@ -330,20 +326,25 @@ headscale:
headscale_api_key_path: p, headscale_api_key_path: p,
token_endpoint_auth_method: 'client_secret_basic', token_endpoint_auth_method: 'client_secret_basic',
disable_api_key_login: false, disable_api_key_login: false,
user_storage_file: '/var/lib/headplane/users.json',
profile_picture_source: 'oidc',
strict_validation: true,
scope: 'openid email profile',
}, },
}); });
await expect( await expect(
loadConfig({ loadEnv: false, path: tempConfigPath }), loadConfig({ loadEnv: false, path: tempConfigPath }),
).rejects.toThrow( ).rejects.toThrow(
/Only one of \"headscale_api_key\" or \"headscale_api_key_path\" may be set/, /Only one of "headscale_api_key" or "headscale_api_key_path" may be set/,
); );
}); });
it('should keep config_path string and interpolate env vars', async () => { it('should keep config_path string and interpolate env vars', async () => {
process.env.MY_HS_CONFIG_SUBDIR = 'hs-test'; process.env.MY_HS_CONFIG_SUBDIR = 'hs-test';
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
const cfgVal = '/etc/headscale-${MY_HS_CONFIG_SUBDIR}/config.yaml'; const cfgVal = '/etc/headscale-${MY_HS_CONFIG_SUBDIR}/config.yaml';
const exp = '/etc/headscale-hs-test/config.yaml'; const exp = '/etc/headscale-hs-test/config.yaml';
const tempConfigPath = writeTempYamlConfig({ const tempConfigPath = await writeTempYamlConfig({
headscale: { config_path: cfgVal }, headscale: { config_path: cfgVal },
}); });
const config = await loadConfig({ loadEnv: true, path: tempConfigPath }); const config = await loadConfig({ loadEnv: true, path: tempConfigPath });
+39
View File
@@ -0,0 +1,39 @@
import { vi } from 'vitest';
const fakeFs = new Map<string, string>();
export function createFakeFile(path: string, content: string) {
fakeFs.set(path, content);
}
export function clearFakeFiles() {
fakeFs.clear();
}
vi.mock(import('node:fs/promises'), async (importOrig) => {
const orig = await importOrig();
return {
...orig,
readFile: (path, options) => {
const p = path.toString();
if (fakeFs.has(p)) {
const content = fakeFs.get(p)!;
if (typeof options === 'string' || options?.encoding) {
return Promise.resolve(content);
}
return Promise.resolve(Buffer.from(content));
}
return orig.readFile.call(this, path, options);
},
access: (path, mode) => {
const p = path.toString();
if (p === '/var/lib/headplane/' || p === '/var/lib/headplane') {
return Promise.resolve();
}
return orig.access.call(this, path, mode);
},
};
});
+21 -11
View File
@@ -15,22 +15,32 @@ if (!version) {
throw new Error('Unable to read version from package.json'); throw new Error('Unable to read version from package.json');
} }
export default defineConfig(({ mode }) => ({ export default defineConfig({
test: { test: {
projects: [
{
extends: true,
test: {
name: 'unit',
include: ['tests/unit/*.test.ts'],
setupFiles: ['tests/unit/setup/overlay-fs.ts'],
},
},
{
extends: true,
test: {
name: 'integration',
include: ['tests/integration/*.test.ts'],
setupFiles: ['tests/integration/setup/vitest-hook.ts'],
testTimeout: 15_000,
},
},
],
env: { env: {
HEADPLANE_DEBUG_LOG: 'true', HEADPLANE_DEBUG_LOG: 'true',
}, },
// bail: mode === 'integration' ? 1 : undefined,
environment: 'node', environment: 'node',
include:
mode === 'integration'
? ['tests/integration/*.test.ts']
: ['tests/**/*.test.js'],
exclude: ['node_modules/**', 'build/**'], exclude: ['node_modules/**', 'build/**'],
setupFiles:
mode === 'integration'
? ['./tests/integration/setup/vitest-hook.ts']
: ['./tests/setupOverlayFs.js'],
}, },
resolve: { resolve: {
alias: { alias: {
@@ -41,4 +51,4 @@ export default defineConfig(({ mode }) => ({
__VERSION__: JSON.stringify(isNext ? `${version}-next` : version), __VERSION__: JSON.stringify(isNext ? `${version}-next` : version),
__PREFIX__: JSON.stringify(prefix), __PREFIX__: JSON.stringify(prefix),
}, },
})); });