File size: 3,238 Bytes
c7d34c1
 
 
 
 
 
 
 
3e8ea5d
 
 
 
 
 
 
 
c7d34c1
 
3e8ea5d
c7d34c1
 
 
 
 
 
 
3e8ea5d
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
3e8ea5d
 
 
 
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e8ea5d
 
 
 
 
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e8ea5d
 
 
 
 
 
 
 
 
c7d34c1
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { setTimeout as delay } from 'node:timers/promises';

const testFiles = ['src/lib/agent-state-postgres.test.ts', 'src/app/api/agent/agent-routes.test.ts'];
const POSTGRES_READY_ATTEMPTS = 30;
const POSTGRES_READY_INTERVAL_MS = 1000;

class CommandFailedError extends Error {
  constructor(command, args, status, stderr = '') {
    super(`${command} ${args.join(' ')} failed with exit code ${status}`);
    this.status = status;
    this.stderr = stderr;
  }
}

function run(command, args, options = {}) {
  const result = spawnSync(command, args, {
    stdio: options.capture || options.silent ? ['ignore', 'pipe', 'pipe'] : 'inherit',
    encoding: 'utf8',
    env: options.env || process.env
  });
  if (options.capture) {
    return result;
  }
  if (result.status !== 0) {
    throw new CommandFailedError(command, args, result.status || 1, result.stderr || '');
  }
  return result;
}

function runTests(databaseUrl) {
  run('node', ['--test', '--import', 'tsx', ...testFiles], {
    env: {
      ...process.env,
      NODE_ENV: 'test',
      AGENT_POSTGRES_TEST_DATABASE_URL: databaseUrl
    }
  });
}

function statusFromError(error) {
  return error instanceof CommandFailedError ? error.status : 1;
}

async function waitForPostgres(containerName) {
  for (let attempt = 0; attempt < POSTGRES_READY_ATTEMPTS; attempt += 1) {
    const result = spawnSync('docker', ['exec', containerName, 'pg_isready', '-U', 'agent_test', '-d', 'agent_test'], {
      stdio: 'ignore'
    });
    if (result.status === 0) return;
    await delay(POSTGRES_READY_INTERVAL_MS);
  }
  throw new Error('临时 PostgreSQL 容器未就绪');
}

function readMappedPort(containerName) {
  const result = run('docker', ['port', containerName, '5432/tcp'], { capture: true });
  if (result.status !== 0) {
    throw new Error(result.stderr || '读取 PostgreSQL 映射端口失败');
  }
  const line = result.stdout.trim().split('\n')[0] || '';
  const match = line.match(/:(\d+)$/);
  if (!match) {
    throw new Error(`docker port 输出不符合预期:${line}`);
  }
  return match[1];
}

if (process.env.AGENT_POSTGRES_TEST_DATABASE_URL) {
  try {
    runTests(process.env.AGENT_POSTGRES_TEST_DATABASE_URL);
  } catch (error) {
    process.exit(statusFromError(error));
  }
  process.exit(0);
}

const containerName = `gpt-image-agent-test-pg-${Date.now()}`;

try {
  run('docker', [
    'run',
    '-d',
    '--name',
    containerName,
    '-e',
    'POSTGRES_DB=agent_test',
    '-e',
    'POSTGRES_USER=agent_test',
    '-e',
    'POSTGRES_PASSWORD=agent_test',
    '-p',
    '127.0.0.1::5432',
    'postgres:16-alpine'
  ], { silent: true });
  await waitForPostgres(containerName);
  const port = readMappedPort(containerName);
  runTests(`postgres://agent_test:agent_test@127.0.0.1:${port}/agent_test`);
} catch (error) {
  process.exitCode = statusFromError(error);
  if (error instanceof CommandFailedError) {
    if (error.stderr.trim()) {
      console.error(error.stderr.trim());
    }
  } else {
    console.error(error instanceof Error ? error.message : String(error));
  }
} finally {
  spawnSync('docker', ['rm', '-f', containerName], { stdio: 'ignore' });
}