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 => { 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 => { 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, ) => { const content = typeof config === 'string' ? config : `${JSON.stringify(config, null, 2)}\n`; await writeFile(configPath(workspace), content); }; const readConfig = async ( workspace: TestWorkspace, ): Promise> => 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'); }); });