feat: support per-provider agent settings and environment-aware analysis

This commit is contained in:
2026-10-20 11:36:28 +09:00
parent ea6ff6997f
commit 94c17ac50d
17 changed files with 452 additions and 56 deletions
@@ -0,0 +1,55 @@
const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const { spawnSync } = require('node:child_process');
const container = `lms-ai-provider-migration-${process.pid}`;
const run = (command, args, input) => {
const result = spawnSync(command, args, { input, encoding: 'utf8', timeout: 30000 });
assert.equal(result.status, 0, result.stderr || result.error?.message);
return result.stdout;
};
const migration = name => readFileSync(resolve(__dirname, '../prisma/migrations', name, 'migration.sql'), 'utf8');
try {
run('docker', ['run', '-d', '--rm', '--name', container, '--network', 'none', '-e', 'POSTGRES_HOST_AUTH_METHOD=trust', 'postgres:16-alpine']);
let ready = false;
for (let attempt = 0; attempt < 100; attempt++) {
if (spawnSync('docker', ['exec', container, 'pg_isready', '-h', '127.0.0.1', '-U', 'postgres'], { stdio: 'ignore' }).status === 0) { ready = true; break; }
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
}
assert(ready, 'Isolated test PostgreSQL did not start');
const sql = `
CREATE TABLE courses (id uuid PRIMARY KEY);
${migration('20261014160000_add_course_ai_settings')}
${migration('20261014170000_add_course_ai_endpoint')}
INSERT INTO courses VALUES ('00000000-0000-0000-0000-000000000001');
INSERT INTO course_ai_settings (id, course_id, enabled, provider, model, encrypted_api_key, endpoint_url, updated_at)
VALUES ('00000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000001', true, 'QWEN', 'qwen3.8-flash', 'preserved-key', 'https://example.test/v1', '2026-10-19');
CREATE TEMP TABLE original AS TABLE course_ai_settings;
${migration('20261020160000_allow_course_ai_providers')}
DO $$ BEGIN
IF EXISTS ((SELECT * FROM original EXCEPT SELECT * FROM course_ai_settings) UNION ALL
(SELECT * FROM course_ai_settings EXCEPT SELECT * FROM original)) THEN
RAISE EXCEPTION 'Existing provider setting was changed';
END IF;
END $$;
INSERT INTO course_ai_settings (id, course_id, provider, model, updated_at)
VALUES ('00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000001', 'OPENAI', 'gpt-6.1-sol', now());
DO $$ BEGIN
IF (SELECT count(*) FROM course_ai_settings) <> 2 THEN RAISE EXCEPTION 'Provider settings are not independent'; END IF;
IF (SELECT enabled FROM course_ai_settings WHERE provider = 'OPENAI') THEN RAISE EXCEPTION 'New providers must default to disabled'; END IF;
BEGIN
INSERT INTO course_ai_settings (id, course_id, provider, model, updated_at)
VALUES ('00000000-0000-0000-0000-000000000004', '00000000-0000-0000-0000-000000000001', 'OPENAI', 'duplicate', now());
RAISE EXCEPTION 'Duplicate provider was accepted';
EXCEPTION WHEN unique_violation THEN NULL;
END;
END $$;
DELETE FROM courses;
DO $$ BEGIN IF EXISTS (SELECT FROM course_ai_settings) THEN RAISE EXCEPTION 'Cascade relation was lost'; END IF; END $$;
`;
run('docker', ['exec', '-i', container, 'psql', '-h', '127.0.0.1', '-U', 'postgres', '-v', 'ON_ERROR_STOP=1'], sql);
console.log('PASS: migration preserves settings and keys, allows multiple providers, defaults new providers to disabled, rejects duplicates, and retains cascade deletion');
} finally {
spawnSync('docker', ['rm', '-f', container], { stdio: 'ignore' });
}