231 lines
7.4 KiB
TypeScript
231 lines
7.4 KiB
TypeScript
import { spawn, spawnSync } from 'node:child_process';
|
|
import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
|
|
type TestWorkspace = {
|
|
binDir: string;
|
|
homeDir: string;
|
|
localFile: string;
|
|
projectDir: string;
|
|
};
|
|
|
|
const scriptPath = fileURLToPath(
|
|
new URL('../../../../scripts/infisical-account', import.meta.url),
|
|
);
|
|
|
|
let workspaces: TestWorkspace[] = [];
|
|
|
|
const createWorkspace = async (): Promise<TestWorkspace> => {
|
|
const root = await realpathTemp();
|
|
const homeDir = path.join(root, 'home');
|
|
const projectDir = path.join(root, 'project');
|
|
const binDir = path.join(root, 'bin');
|
|
const localFile = path.join(projectDir, '.local', 'infisical.env');
|
|
|
|
await mkdir(path.join(homeDir, '.infisical'), { recursive: true });
|
|
await mkdir(path.dirname(localFile), { recursive: true });
|
|
await mkdir(binDir, { recursive: true });
|
|
|
|
const fakeInfisical = path.join(binDir, 'infisical');
|
|
await writeFile(fakeInfisical, '#!/usr/bin/env sh\nexit 0\n');
|
|
await chmod(fakeInfisical, 0o755);
|
|
|
|
const workspace = { binDir, homeDir, localFile, projectDir };
|
|
workspaces.push(workspace);
|
|
return workspace;
|
|
};
|
|
|
|
const realpathTemp = async (): Promise<string> => {
|
|
const base = path.join(tmpdir(), 'spoon-infisical-account-');
|
|
const { mkdtemp } = await import('node:fs/promises');
|
|
return mkdtemp(base);
|
|
};
|
|
|
|
const configPath = (workspace: TestWorkspace) =>
|
|
path.join(workspace.homeDir, '.infisical', 'infisical-config.json');
|
|
|
|
const writeConfig = async (
|
|
workspace: TestWorkspace,
|
|
config: Record<string, unknown> | string,
|
|
) => {
|
|
const content =
|
|
typeof config === 'string'
|
|
? config
|
|
: `${JSON.stringify(config, null, 2)}\n`;
|
|
await writeFile(configPath(workspace), content);
|
|
};
|
|
|
|
const readConfig = async (
|
|
workspace: TestWorkspace,
|
|
): Promise<Record<string, unknown>> =>
|
|
JSON.parse(await readFile(configPath(workspace), 'utf8')) as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
|
|
const envFor = (workspace: TestWorkspace): NodeJS.ProcessEnv => ({
|
|
...process.env,
|
|
HOME: workspace.homeDir,
|
|
PATH: `${workspace.binDir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,
|
|
SPOON_INFISICAL_LOCAL_FILE: workspace.localFile,
|
|
});
|
|
|
|
const runEnsure = (workspace: TestWorkspace) =>
|
|
spawnSync(scriptPath, ['ensure'], {
|
|
encoding: 'utf8',
|
|
env: envFor(workspace),
|
|
});
|
|
|
|
const writeLocalEmail = async (workspace: TestWorkspace, emailLine: string) => {
|
|
await mkdir(path.dirname(workspace.localFile), { recursive: true });
|
|
await writeFile(workspace.localFile, `${emailLine}\n`);
|
|
};
|
|
|
|
const twoAccountConfig = {
|
|
loggedInUsers: [
|
|
{ email: 'work@example.com', domain: 'https://app.infisical.com' },
|
|
{ email: 'home@example.com', domain: 'https://infisical.gbrown.org' },
|
|
],
|
|
loggedInUserEmail: 'work@example.com',
|
|
LoggedInUserDomain: 'https://app.infisical.com',
|
|
};
|
|
|
|
beforeEach(() => {
|
|
workspaces = [];
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
workspaces.map((workspace) =>
|
|
rm(path.dirname(workspace.homeDir), { force: true, recursive: true }),
|
|
),
|
|
);
|
|
});
|
|
|
|
describe('infisical-account', () => {
|
|
test('single account no-ops without local file', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, {
|
|
loggedInUsers: [
|
|
{ email: 'work@example.com', domain: 'https://app.infisical.com' },
|
|
],
|
|
loggedInUserEmail: 'work@example.com',
|
|
LoggedInUserDomain: 'https://app.infisical.com',
|
|
});
|
|
|
|
const result = runEnsure(workspace);
|
|
const config = await readConfig(workspace);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(config.loggedInUserEmail).toBe('work@example.com');
|
|
expect(config.LoggedInUserDomain).toBe('https://app.infisical.com');
|
|
});
|
|
|
|
test('multiple accounts require local project config', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, twoAccountConfig);
|
|
|
|
const result = runEnsure(workspace);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain('.local/infisical.env');
|
|
expect(result.stderr).toContain('work@example.com');
|
|
expect(result.stderr).toContain('home@example.com');
|
|
});
|
|
|
|
test('multiple accounts switch to configured email', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, twoAccountConfig);
|
|
await writeLocalEmail(workspace, 'INFISICAL_EMAIL=home@example.com');
|
|
|
|
const result = runEnsure(workspace);
|
|
const config = await readConfig(workspace);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(config.loggedInUserEmail).toBe('home@example.com');
|
|
expect(config.LoggedInUserDomain).toBe('https://infisical.gbrown.org');
|
|
});
|
|
|
|
test('configured email missing from local accounts fails clearly', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, twoAccountConfig);
|
|
await writeLocalEmail(workspace, 'INFISICAL_EMAIL=missing@example.com');
|
|
|
|
const result = runEnsure(workspace);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
'not logged in locally: missing@example.com',
|
|
);
|
|
});
|
|
|
|
test.each([
|
|
'INFISICAL_EMAIL="home@example.com"',
|
|
"INFISICAL_EMAIL='home@example.com'",
|
|
])('quoted email parses correctly: %s', async (line) => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, twoAccountConfig);
|
|
await writeLocalEmail(workspace, line);
|
|
|
|
const result = runEnsure(workspace);
|
|
const config = await readConfig(workspace);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(config.loggedInUserEmail).toBe('home@example.com');
|
|
});
|
|
|
|
test('empty email fails clearly', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, twoAccountConfig);
|
|
await writeLocalEmail(workspace, 'INFISICAL_EMAIL=');
|
|
|
|
const result = runEnsure(workspace);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
'.local/infisical.env must contain INFISICAL_EMAIL',
|
|
);
|
|
});
|
|
|
|
test('corrupt config fails clearly', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, '{not-json');
|
|
|
|
const result = runEnsure(workspace);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
'Infisical config is invalid or missing loggedInUsers',
|
|
);
|
|
});
|
|
|
|
test('concurrent ensure calls do not corrupt config', async () => {
|
|
const workspace = await createWorkspace();
|
|
await writeConfig(workspace, twoAccountConfig);
|
|
await writeLocalEmail(workspace, 'INFISICAL_EMAIL=home@example.com');
|
|
|
|
const run = () =>
|
|
new Promise<{ status: number | null; stderr: string }>((resolve) => {
|
|
const child = spawn(scriptPath, ['ensure'], { env: envFor(workspace) });
|
|
let stderr = '';
|
|
child.stderr.on('data', (chunk: Buffer) => {
|
|
stderr += chunk.toString('utf8');
|
|
});
|
|
child.on('close', (status) => {
|
|
resolve({ status, stderr });
|
|
});
|
|
});
|
|
|
|
const [first, second] = await Promise.all([run(), run()]);
|
|
const config = await readConfig(workspace);
|
|
|
|
expect(first).toEqual({ status: 0, stderr: '' });
|
|
expect(second).toEqual({ status: 0, stderr: '' });
|
|
expect(config.loggedInUserEmail).toBe('home@example.com');
|
|
expect(config.LoggedInUserDomain).toBe('https://infisical.gbrown.org');
|
|
});
|
|
});
|