mirror of
https://github.com/tale/headplane.git
synced 2026-07-28 00:28:56 +00:00
chore: refactor unit tests
This commit is contained in:
@@ -46,7 +46,7 @@ jobs:
|
||||
run: ./build.sh --skip-pnpm-prune
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
run: pnpm run test:unit
|
||||
|
||||
- name: Run integration tests
|
||||
run: pnpm run test:integration
|
||||
|
||||
+2
-3
@@ -10,8 +10,8 @@
|
||||
"dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev",
|
||||
"start": "node build/server/index.js",
|
||||
"typecheck": "react-router typegen && tsgo",
|
||||
"test": "vitest run",
|
||||
"test:integration": "vitest run --mode integration",
|
||||
"test:unit": "vitest run --project unit",
|
||||
"test:integration": "vitest run --project integration",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs",
|
||||
@@ -56,7 +56,6 @@
|
||||
"drizzle-orm": "0.44.7",
|
||||
"isbot": "5.1.32",
|
||||
"jose": "6.1.2",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lefthook": "^2.0.4",
|
||||
"lucide-react": "^0.540.0",
|
||||
"mime": "^4.1.0",
|
||||
|
||||
Generated
-3
@@ -122,9 +122,6 @@ importers:
|
||||
jose:
|
||||
specifier: 6.1.2
|
||||
version: 6.1.2
|
||||
js-yaml:
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1
|
||||
lefthook:
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import yaml from 'js-yaml';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { overlayFS } from './setupOverlayFs.js';
|
||||
import { mkdtemp, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { stringify } from 'yaml';
|
||||
import { ConfigError, loadConfig } from '~/server/config/loader';
|
||||
import { HeadplaneConfig } from '~/server/config/schema';
|
||||
import { clearFakeFiles, createFakeFile } from './setup/overlay-fs';
|
||||
|
||||
// Get the directory name in ESM
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
async function writeTempFile(baseName: string, content: string) {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'headplane-test-'));
|
||||
|
||||
// Helper to create a temp file with given content, return its path
|
||||
function writeTempFile(baseName, content) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'headplane-test-'));
|
||||
const filePath = path.join(dir, baseName);
|
||||
fs.writeFileSync(filePath, content);
|
||||
return filePath;
|
||||
const path = join(dir, baseName);
|
||||
await writeFile(path, content);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Helper to create a temporary YAML config file with minimal required structure
|
||||
function writeTempYamlConfig(customConfig = {}) {
|
||||
type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
||||
};
|
||||
|
||||
function writeTempYamlConfig(
|
||||
customConfig: RecursivePartial<HeadplaneConfig> | string = {},
|
||||
) {
|
||||
const defaultConfig = {
|
||||
debug: false,
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
data_path: '/var/lib/headplane',
|
||||
cookie_secret: '12345678901234567890123456789012',
|
||||
cookie_secure: false,
|
||||
agent: {
|
||||
authkey: 'testagentauthkey',
|
||||
ttl: 180000,
|
||||
cache_path: '/tmp/agent_cache.json',
|
||||
},
|
||||
cookie_max_age: 86400,
|
||||
},
|
||||
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 };
|
||||
if (isObject(target) && isObject(source)) {
|
||||
output = { ...target, ...source };
|
||||
for (const key of Object.keys(source)) {
|
||||
if (isObject(source[key]) && key in target && isObject(target[key])) {
|
||||
output[key] = deepMerge(target[key], source[key]);
|
||||
const sourceValue = source[key as keyof typeof source];
|
||||
const targetValue = target[key as keyof typeof target];
|
||||
if (isObject(sourceValue) && key in target && isObject(targetValue)) {
|
||||
output[key] = deepMerge(targetValue, sourceValue);
|
||||
} else {
|
||||
output[key] = source[key];
|
||||
}
|
||||
@@ -73,13 +75,14 @@ function writeTempYamlConfig(customConfig = {}) {
|
||||
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);
|
||||
}
|
||||
|
||||
const merged = deepMerge(defaultConfig, customConfig);
|
||||
const yamlContent =
|
||||
typeof customConfig === 'string' ? customConfig : yaml.dump(merged);
|
||||
typeof customConfig === 'string' ? customConfig : stringify(merged);
|
||||
return writeTempFile('config.yaml', yamlContent);
|
||||
}
|
||||
|
||||
@@ -87,60 +90,43 @@ function writeTempYamlConfig(customConfig = {}) {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
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(() => {
|
||||
// biome-ignore lint/performance/noDelete: needed for test cleanup
|
||||
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;
|
||||
// biome-ignore lint/performance/noDelete: needed for test cleanup
|
||||
delete process.env.HEADPLANE_HEADSCALE__API_KEY_PATH;
|
||||
// biome-ignore lint/performance/noDelete: needed for test cleanup
|
||||
delete process.env.TEST_SECRET_DIR;
|
||||
|
||||
// Clear overlayfs
|
||||
overlayFS.clear();
|
||||
|
||||
// Create default files that the config expects
|
||||
overlayFS.createFile(
|
||||
'/var/lib/headplane/agent_cache.json',
|
||||
JSON.stringify({}),
|
||||
);
|
||||
overlayFS.createFile(
|
||||
clearFakeFiles();
|
||||
createFakeFile('/var/lib/headplane/agent_cache.json', JSON.stringify({}));
|
||||
createFakeFile(
|
||||
'/var/lib/headplane/users.json',
|
||||
`[{"u":"acb3294f89a16b554e06b80d5266a3c8b09a883e1fa78ac459a550bf52a32564","c":65535}]`,
|
||||
);
|
||||
overlayFS.createFile('/tmp/agent_cache.json', JSON.stringify({}));
|
||||
|
||||
// Create test files that some tests expect to exist
|
||||
overlayFS.createFile('irrelevant', 'irrelevant-content');
|
||||
overlayFS.createFile('placeholder', 'placeholder-content');
|
||||
createFakeFile('/tmp/agent_cache.json', JSON.stringify({}));
|
||||
createFakeFile('irrelevant', 'irrelevant-content');
|
||||
createFakeFile('placeholder', 'placeholder-content');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Object.assign(process.env, originalEnv);
|
||||
overlayFS.clear();
|
||||
clearFakeFiles();
|
||||
});
|
||||
|
||||
describe('OIDC Configuration', () => {
|
||||
const minimalOidcFields = {
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
token_endpoint_auth_method: 'client_secret_basic' as const,
|
||||
disable_api_key_login: false,
|
||||
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 () => {
|
||||
const secretValue = 'yaml-file-oidc-secret';
|
||||
const secretPath = writeTempFile('oidc_secret.txt', secretValue);
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const secretPath = await writeTempFile('oidc_secret.txt', secretValue);
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
@@ -149,19 +135,19 @@ describe('Configuration Loading', () => {
|
||||
},
|
||||
});
|
||||
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 () => {
|
||||
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;
|
||||
// Instead of 'irrelevant', use a temp file that exists
|
||||
const irrelevantPath = writeTempFile(
|
||||
const irrelevantPath = await writeTempFile(
|
||||
'irrelevant.txt',
|
||||
'irrelevant-content',
|
||||
);
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
@@ -170,25 +156,25 @@ describe('Configuration Loading', () => {
|
||||
},
|
||||
});
|
||||
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 () => {
|
||||
const value = 'interpolated-secret';
|
||||
const dir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'headplane-secret-dir-'),
|
||||
);
|
||||
const file = path.join(dir, 'secret.txt');
|
||||
fs.writeFileSync(file, value);
|
||||
const dir = await mkdtemp(join(tmpdir(), 'headplane-secret-dir-'));
|
||||
const filePath = join(dir, 'secret.txt');
|
||||
await writeFile(filePath, value);
|
||||
|
||||
process.env.TEST_SECRET_DIR = dir;
|
||||
process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH =
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
|
||||
'${TEST_SECRET_DIR}/secret.txt';
|
||||
// Instead of 'placeholder', use a temp file that exists
|
||||
const placeholderPath = writeTempFile(
|
||||
const placeholderPath = await writeTempFile(
|
||||
'placeholder.txt',
|
||||
'placeholder-content',
|
||||
);
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
@@ -197,11 +183,11 @@ describe('Configuration Loading', () => {
|
||||
},
|
||||
});
|
||||
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 () => {
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
@@ -216,8 +202,9 @@ describe('Configuration Loading', () => {
|
||||
|
||||
it('should reject when client_secret_path has unresolvable env var interpolation', async () => {
|
||||
process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH =
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
|
||||
'${MISSING_DIR}/secret.txt';
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
@@ -234,7 +221,7 @@ describe('Configuration Loading', () => {
|
||||
describe('Server Configuration', () => {
|
||||
it('should load cookie_secret directly from YAML', async () => {
|
||||
const valid = 'abcdefghijklmnopqrstuvwxyz123456';
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
server: { cookie_secret: valid },
|
||||
});
|
||||
const config = await loadConfig({ loadEnv: false, path: tempConfigPath });
|
||||
@@ -243,8 +230,8 @@ describe('Configuration Loading', () => {
|
||||
|
||||
it('should load cookie_secret from file', async () => {
|
||||
const secret = 'a'.repeat(32);
|
||||
const secretPath = writeTempFile('cookie_secret.txt', secret);
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const secretPath = await writeTempFile('cookie_secret.txt', secret);
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
server: { cookie_secret_path: secretPath },
|
||||
});
|
||||
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 () => {
|
||||
const secretPath = writeTempFile('conflict.txt', 'x'.repeat(32));
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const secretPath = await writeTempFile('conflict.txt', 'x'.repeat(32));
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
server: {
|
||||
cookie_secret: '1'.repeat(32),
|
||||
cookie_secret_path: secretPath,
|
||||
@@ -262,67 +249,76 @@ describe('Configuration Loading', () => {
|
||||
await expect(
|
||||
loadConfig({ loadEnv: false, path: tempConfigPath }),
|
||||
).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 () => {
|
||||
const yaml = `debug: false
|
||||
const yaml = `
|
||||
debug: false
|
||||
server:
|
||||
host: \"127.0.0.1\"
|
||||
host: "127.0.0.1"
|
||||
port: 8080
|
||||
cookie_secure: false
|
||||
agent:
|
||||
authkey: \"key\"
|
||||
authkey: "key"
|
||||
ttl: 180000
|
||||
cache_path: \"/tmp/cache.json\"
|
||||
cache_path: "/tmp/cache.json"
|
||||
headscale:
|
||||
url: \"http://localhost\"
|
||||
url: "http://localhost"
|
||||
config_strict: false
|
||||
`;
|
||||
const tempConfigPath = writeTempYamlConfig(yaml);
|
||||
const tempConfigPath = await writeTempYamlConfig(yaml);
|
||||
await expect(
|
||||
loadConfig({ loadEnv: false, path: tempConfigPath }),
|
||||
).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', () => {
|
||||
it('should load headscale_api_key directly from YAML', async () => {
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
headscale_api_key: 'hs-yaml-key',
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
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 });
|
||||
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 () => {
|
||||
const val = 'hs-file-key';
|
||||
const p = writeTempFile('hs_api_key.txt', val);
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const p = await writeTempFile('hs_api_key.txt', val);
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
headscale_api_key_path: p,
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
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 });
|
||||
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 () => {
|
||||
const p = writeTempFile('conflict.txt', 'irrelevant');
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const p = await writeTempFile('conflict.txt', 'irrelevant');
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
oidc: {
|
||||
issuer: 'https://example.com/oidc',
|
||||
client_id: 'test',
|
||||
@@ -330,20 +326,25 @@ headscale:
|
||||
headscale_api_key_path: p,
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
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(
|
||||
loadConfig({ loadEnv: false, path: tempConfigPath }),
|
||||
).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 () => {
|
||||
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 exp = '/etc/headscale-hs-test/config.yaml';
|
||||
const tempConfigPath = writeTempYamlConfig({
|
||||
const tempConfigPath = await writeTempYamlConfig({
|
||||
headscale: { config_path: cfgVal },
|
||||
});
|
||||
const config = await loadConfig({ loadEnv: true, path: tempConfigPath });
|
||||
@@ -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
@@ -15,22 +15,32 @@ if (!version) {
|
||||
throw new Error('Unable to read version from package.json');
|
||||
}
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
export default defineConfig({
|
||||
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: {
|
||||
HEADPLANE_DEBUG_LOG: 'true',
|
||||
},
|
||||
// bail: mode === 'integration' ? 1 : undefined,
|
||||
environment: 'node',
|
||||
include:
|
||||
mode === 'integration'
|
||||
? ['tests/integration/*.test.ts']
|
||||
: ['tests/**/*.test.js'],
|
||||
exclude: ['node_modules/**', 'build/**'],
|
||||
setupFiles:
|
||||
mode === 'integration'
|
||||
? ['./tests/integration/setup/vitest-hook.ts']
|
||||
: ['./tests/setupOverlayFs.js'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -41,4 +51,4 @@ export default defineConfig(({ mode }) => ({
|
||||
__VERSION__: JSON.stringify(isNext ? `${version}-next` : version),
|
||||
__PREFIX__: JSON.stringify(prefix),
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user