POST /recommend now calls ml/serving /recommend with pre-computed agent snippets + task context instead of /generate + /score/egreedy/v2. Falls back to a random signal candidate when ml/serving is unavailable. Removes: remotePolicy, fetchLlmCandidates, sendRewardWithRetry, candidateCache, pickPromptVersion. Feedback handler keeps inferReward + tipFeedback writes for observability; reward delivery to the bandit is gone. tipScores.policy is now 'orchestrator'; promptVersion is 'v4-orchestrator'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
4.9 KiB
TypeScript
171 lines
4.9 KiB
TypeScript
/**
|
|
* Creates an isolated in-memory SQLite DB with the full schema applied.
|
|
* Use this in tests instead of the shared `db` singleton.
|
|
*/
|
|
import Database, { type Database as BetterSqlite3Database } from 'better-sqlite3';
|
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
import * as schema from '../db/schema.js';
|
|
|
|
type DrizzleDb = ReturnType<typeof drizzle<typeof schema>>;
|
|
|
|
export function makeTestDb(): DrizzleDb & { rawSqlite: BetterSqlite3Database } {
|
|
const sqlite = new Database(':memory:');
|
|
sqlite.pragma('foreign_keys = ON');
|
|
|
|
sqlite.exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
email TEXT NOT NULL UNIQUE,
|
|
name TEXT,
|
|
image TEXT,
|
|
google_id TEXT UNIQUE,
|
|
role TEXT NOT NULL DEFAULT 'user',
|
|
consent_given INTEGER NOT NULL DEFAULT 0,
|
|
consent_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
deleted_at TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS integration_tokens (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
provider TEXT NOT NULL,
|
|
access_token TEXT NOT NULL,
|
|
refresh_token TEXT,
|
|
expires_at TEXT,
|
|
connected_at TEXT NOT NULL,
|
|
token_status TEXT NOT NULL DEFAULT 'active',
|
|
UNIQUE(user_id, provider)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tip_feedback (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
tip_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
source_id TEXT,
|
|
dwell_ms INTEGER,
|
|
reward_milli INTEGER,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tip_views (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
tip_id TEXT NOT NULL,
|
|
served_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
endpoint TEXT NOT NULL UNIQUE,
|
|
p256dh TEXT NOT NULL,
|
|
auth TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
expires_at TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS admin_actions (
|
|
id TEXT PRIMARY KEY,
|
|
admin_id TEXT NOT NULL REFERENCES users(id),
|
|
action TEXT NOT NULL,
|
|
target_type TEXT,
|
|
target_id TEXT,
|
|
detail TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tip_scores (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
tip_id TEXT NOT NULL,
|
|
policy TEXT NOT NULL,
|
|
ml_score INTEGER,
|
|
features_json TEXT,
|
|
candidate_count INTEGER,
|
|
latency_ms INTEGER,
|
|
served_at TEXT NOT NULL,
|
|
prompt_version TEXT,
|
|
llm_model TEXT,
|
|
tip_kind TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS saved_queries (
|
|
id TEXT PRIMARY KEY,
|
|
admin_id TEXT NOT NULL REFERENCES users(id),
|
|
name TEXT NOT NULL,
|
|
sql TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_profile_features (
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
name TEXT NOT NULL,
|
|
value REAL,
|
|
value_text TEXT,
|
|
updated_at TEXT NOT NULL,
|
|
ttl_sec INTEGER NOT NULL,
|
|
PRIMARY KEY (user_id, name)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sim_runs (
|
|
id TEXT PRIMARY KEY,
|
|
policy_a TEXT NOT NULL,
|
|
policy_b TEXT NOT NULL,
|
|
n_users INTEGER NOT NULL,
|
|
n_rounds INTEGER NOT NULL,
|
|
tasks_per_round INTEGER NOT NULL DEFAULT 8,
|
|
use_llm INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
summary_json TEXT,
|
|
winner TEXT,
|
|
persona_breakdown_json TEXT,
|
|
created_at TEXT NOT NULL,
|
|
finished_at TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS agent_outputs (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
agent_id TEXT NOT NULL,
|
|
prompt_text TEXT NOT NULL,
|
|
signals_snapshot TEXT,
|
|
computed_at TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
agent_version TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sim_events (
|
|
id TEXT PRIMARY KEY,
|
|
run_id TEXT NOT NULL REFERENCES sim_runs(id),
|
|
round INTEGER NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
persona TEXT NOT NULL,
|
|
policy TEXT NOT NULL,
|
|
tip_content TEXT NOT NULL,
|
|
priority INTEGER NOT NULL,
|
|
is_overdue INTEGER NOT NULL,
|
|
action TEXT NOT NULL,
|
|
dwell_ms INTEGER,
|
|
reward_milli INTEGER NOT NULL,
|
|
hour INTEGER NOT NULL,
|
|
day_of_week INTEGER NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
`);
|
|
|
|
const db = drizzle(sqlite, { schema });
|
|
// `sqlite` is exposed as `rawSqlite` so tests that mock `../db/index.js`
|
|
// can provide the same `{ db, rawSqlite }` shape as the prod module.
|
|
return Object.assign(db, { rawSqlite: sqlite });
|
|
}
|
|
|
|
export type TestDb = ReturnType<typeof makeTestDb>;
|