fix(M-AI-01-09): enqueue jobs via BullMQ instead of MySQL INSERT
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 47s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 47s
Worker consumes from BullMQ Redis, not MySQL polling. Replace all dbExec(INSERT INTO AiAnalysisJob) with enqueueJob() which uses real BullMQ Queue.add() to the test Redis. Add setRedisConfig() + enqueueJob() to integration harness. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
bda7f9d80a
commit
5bde0a3656
@ -5,6 +5,29 @@
|
||||
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import * as http from 'http';
|
||||
import { Queue } from 'bullmq';
|
||||
|
||||
let redisConn: { host: string; port: number; password?: string } | null = null;
|
||||
|
||||
export function setRedisConfig(host: string, port: number, password?: string) {
|
||||
redisConn = { host, port, password: password || undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a job directly to BullMQ Redis queue.
|
||||
* Worker process consumes from this queue.
|
||||
*/
|
||||
export async function enqueueJob(queueName: string, data: Record<string, unknown>, jobId?: string): Promise<string> {
|
||||
if (!redisConn) throw new Error('Redis config not set — call setRedisConfig() first');
|
||||
const queue = new Queue(queueName, { connection: redisConn });
|
||||
const job = await queue.add(queueName, data, {
|
||||
attempts: 3,
|
||||
backoff: { type: 'exponential', delay: 1000 },
|
||||
...(jobId ? { jobId } : {}),
|
||||
});
|
||||
await queue.close();
|
||||
return job.id as string;
|
||||
}
|
||||
|
||||
let apiProc: ChildProcess | null = null;
|
||||
let workerProc: ChildProcess | null = null;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
*
|
||||
* 使用真实 MySQL / Redis / BullMQ / HTTP。
|
||||
* 通过启动真实 API 和 Worker 进程 + Mock AI Provider 验证完整业务链路。
|
||||
* 测试通过 BullMQ Redis 入队 → Worker 消费 → MySQL 落库 全链路。
|
||||
*
|
||||
* 运行:
|
||||
* DATABASE_URL="mysql://..." REDIS_HOST=... \
|
||||
@ -11,7 +12,8 @@
|
||||
|
||||
import {
|
||||
startAPI, startWorker, stopAPI, stopWorker, getApiPid, getWorkerPid,
|
||||
httpGet, httpPost, setupFixtures, cleanupFixtures, dbQuery, dbExec, dbClose,
|
||||
httpGet, setupFixtures, cleanupFixtures, dbQuery, dbClose,
|
||||
enqueueJob, setRedisConfig,
|
||||
} from './helpers/integration-harness';
|
||||
import { MockAIProvider } from './helpers/mock-ai-provider';
|
||||
|
||||
@ -22,7 +24,7 @@ const TEST_SESSION = `itg-sess-${Math.random().toString(36).slice(2, 8)}`;
|
||||
|
||||
const DB_URL = process.env.DATABASE_URL || 'mysql://zhixi_user:test@127.0.0.1:3306/zhixi_test';
|
||||
const REDIS_HOST = process.env.REDIS_HOST || '127.0.0.1';
|
||||
const REDIS_PORT = process.env.REDIS_PORT || '6379';
|
||||
const REDIS_PORT = parseInt(process.env.REDIS_PORT || '6379', 10);
|
||||
const REDIS_PW = process.env.REDIS_PASSWORD || '';
|
||||
|
||||
let mockAI: MockAIProvider;
|
||||
@ -32,19 +34,22 @@ beforeAll(async () => {
|
||||
mockAI = new MockAIProvider();
|
||||
mockAI.setNormal();
|
||||
await mockAI.start();
|
||||
console.log(`[test] Mock AI Provider at ${mockAI.url}`);
|
||||
|
||||
// 2. Setup test fixtures
|
||||
// 2. Setup fixtures (User, KB, KI, Session in DB)
|
||||
await setupFixtures(DB_URL, TEST_USER, TEST_KB, TEST_KI, TEST_SESSION);
|
||||
|
||||
// 3. Start API
|
||||
const apiPid = await startAPI(mockAI.url, DB_URL, REDIS_HOST, REDIS_PORT, REDIS_PW);
|
||||
// 3. Set Redis config for BullMQ enqueue helper
|
||||
setRedisConfig(REDIS_HOST, REDIS_PORT, REDIS_PW || undefined);
|
||||
|
||||
// 4. Start API
|
||||
const apiPid = await startAPI(mockAI.url, DB_URL, REDIS_HOST, String(REDIS_PORT), REDIS_PW);
|
||||
console.log(`[test] API started, pid=${apiPid}`);
|
||||
|
||||
// 4. Start Worker
|
||||
const workerPid = await startWorker(DB_URL, REDIS_HOST, REDIS_PORT, REDIS_PW, `test-worker-${Date.now()}`);
|
||||
// 5. Start Worker
|
||||
const workerPid = await startWorker(DB_URL, REDIS_HOST, String(REDIS_PORT), REDIS_PW, `test-worker-${Date.now()}`);
|
||||
console.log(`[test] Worker started, pid=${workerPid}`);
|
||||
|
||||
// 5. Wait for Worker to be ready
|
||||
await new Promise(r => setTimeout(r, 3000));
|
||||
}, 60_000);
|
||||
|
||||
@ -59,8 +64,6 @@ afterAll(async () => {
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
describe('M-AI-01-09 真实集成测试', () => {
|
||||
|
||||
// ── 0. 环境验证 ──
|
||||
|
||||
describe('环境验证', () => {
|
||||
it('API 健康检查', async () => {
|
||||
const res = await httpGet('/health');
|
||||
@ -69,195 +72,199 @@ describe('M-AI-01-09 真实集成测试', () => {
|
||||
});
|
||||
|
||||
it('API PID 记录', () => {
|
||||
const pid = getApiPid();
|
||||
expect(pid).toBeGreaterThan(0);
|
||||
console.log(` API PID: ${pid}`);
|
||||
expect(getApiPid()).toBeGreaterThan(0);
|
||||
console.log(` API PID: ${getApiPid()}`);
|
||||
});
|
||||
|
||||
it('Worker PID 记录', () => {
|
||||
const pid = getWorkerPid();
|
||||
expect(pid).toBeGreaterThan(0);
|
||||
console.log(` Worker PID: ${pid}`);
|
||||
expect(getWorkerPid()).toBeGreaterThan(0);
|
||||
console.log(` Worker PID: ${getWorkerPid()}`);
|
||||
});
|
||||
|
||||
it('MySQL 连接正常', async () => {
|
||||
const rows = await dbQuery(DB_URL, 'SELECT 1 as ok');
|
||||
const rows = await dbQuery(DB_URL, 'SELECT 1 as ok', []);
|
||||
expect(Number((rows[0] as any).ok)).toBe(1);
|
||||
});
|
||||
|
||||
it('Redis 连接正常', async () => {
|
||||
const res = await httpGet('/health');
|
||||
expect(res.data.data.redis).toBe('connected');
|
||||
});
|
||||
});
|
||||
|
||||
// ── 1. Active Recall 完整闭环 ──
|
||||
// ── Active Recall: 通过 BullMQ 入队 → Worker 消费 → 落库 ──
|
||||
|
||||
describe('Active Recall 完整闭环', () => {
|
||||
let jobId: string;
|
||||
let bullJobId: string;
|
||||
|
||||
it('通过 API 入队 AI 分析任务', async () => {
|
||||
// Create an AiAnalysisJob via the repository
|
||||
await dbExec(DB_URL,
|
||||
`INSERT INTO AiAnalysisJob (id, userId, jobType, sessionId, status, queuedAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, 'pending', NOW(), NOW())`,
|
||||
[`ar-job-1`, TEST_USER, 'active-recall', TEST_SESSION]);
|
||||
|
||||
jobId = 'ar-job-1';
|
||||
console.log(` AiAnalysisJob ID: ${jobId}`);
|
||||
expect(jobId).toBeDefined();
|
||||
it('通过 BullMQ 入队 ai-analysis job', async () => {
|
||||
bullJobId = await enqueueJob('ai-analysis', {
|
||||
userId: TEST_USER,
|
||||
type: 'active-recall',
|
||||
questionText: 'What is the capital of France?',
|
||||
knowledgeItemContent: 'France is a country in Europe. Its capital is Paris.',
|
||||
userAnswer: 'Paris',
|
||||
sessionId: TEST_SESSION,
|
||||
answerId: `test-answer-${Date.now()}`,
|
||||
});
|
||||
console.log(` BullMQ Job ID: ${bullJobId}`);
|
||||
expect(bullJobId).toBeDefined();
|
||||
});
|
||||
|
||||
it('Worker 消费任务并生成 AiAnalysisResult', async () => {
|
||||
it('Worker 消费后 AiAnalysisJob 写入 MySQL', async () => {
|
||||
await new Promise(r => setTimeout(r, 10_000));
|
||||
|
||||
const results = await dbQuery(DB_URL,
|
||||
'SELECT id, userId, jobId FROM AiAnalysisResult WHERE userId = ?',
|
||||
[TEST_USER]);
|
||||
console.log(` AiAnalysisResult count: ${results.length}`);
|
||||
if (results.length > 0) {
|
||||
console.log(` AiAnalysisResult ID: ${(results[0] as any).id}`);
|
||||
}
|
||||
const rows = await dbQuery(DB_URL,
|
||||
'SELECT id, status FROM AiAnalysisJob WHERE userId = ? ORDER BY createdAt DESC', [TEST_USER]);
|
||||
console.log(` AiAnalysisJobs found: ${rows.length}`);
|
||||
for (const r of rows) console.log(` id=${(r as any).id} status=${(r as any).status}`);
|
||||
}, 15_000);
|
||||
|
||||
it('Job 最终 completed', async () => {
|
||||
it('AiAnalysisResult 创建', async () => {
|
||||
const rows = await dbQuery(DB_URL,
|
||||
'SELECT status FROM AiAnalysisJob WHERE id = ?', [jobId]);
|
||||
console.log(` Job status: ${(rows[0] as any)?.status}`);
|
||||
'SELECT id, userId, jobId FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
console.log(` AiAnalysisResults: ${rows.length}`);
|
||||
for (const r of rows) console.log(` id=${(r as any).id} jobId=${(r as any).jobId}`);
|
||||
});
|
||||
|
||||
it('AiAnalysisResult 不超过 1 个', async () => {
|
||||
const rows = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?',
|
||||
[TEST_USER]);
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
expect(Number((rows[0] as any).cnt)).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('FocusItem 正确归属', async () => {
|
||||
const rows = await dbQuery(DB_URL,
|
||||
'SELECT id, userId, source FROM FocusItem WHERE userId = ?',
|
||||
[TEST_USER]);
|
||||
console.log(` FocusItem count: ${rows.length}`);
|
||||
for (const r of rows) {
|
||||
console.log(` FocusItem ID: ${(r as any).id}, source: ${(r as any).source}`);
|
||||
expect((r as any).userId).toBe(TEST_USER);
|
||||
}
|
||||
'SELECT id, userId, source FROM FocusItem WHERE userId = ?', [TEST_USER]);
|
||||
console.log(` FocusItems: ${rows.length}`);
|
||||
for (const r of rows) expect((r as any).userId).toBe(TEST_USER);
|
||||
});
|
||||
|
||||
it('ReviewCard 创建', async () => {
|
||||
const rows = await dbQuery(DB_URL,
|
||||
'SELECT id, userId FROM ReviewCard WHERE userId = ?',
|
||||
[TEST_USER]);
|
||||
console.log(` ReviewCard count: ${rows.length}`);
|
||||
for (const r of rows) {
|
||||
console.log(` ReviewCard ID: ${(r as any).id}`);
|
||||
expect((r as any).userId).toBe(TEST_USER);
|
||||
}
|
||||
'SELECT id, userId FROM ReviewCard WHERE userId = ?', [TEST_USER]);
|
||||
console.log(` ReviewCards: ${rows.length}`);
|
||||
for (const r of rows) expect((r as any).userId).toBe(TEST_USER);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 2. 幂等性 ──
|
||||
// ── 幂等性: 重复 jobId ──
|
||||
|
||||
describe('幂等性', () => {
|
||||
it('重复添加相同 job 不产生重复实体', async () => {
|
||||
it('重复 jobId 不产生重复实体', async () => {
|
||||
const dupJobId = `dup-test-${Date.now()}`;
|
||||
await enqueueJob('ai-analysis', {
|
||||
userId: TEST_USER,
|
||||
type: 'active-recall',
|
||||
questionText: 'Q?', knowledgeItemContent: 'C.', userAnswer: 'A',
|
||||
}, dupJobId);
|
||||
|
||||
const before = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
const beforeCnt = Number((before[0] as any).cnt);
|
||||
|
||||
await dbExec(DB_URL,
|
||||
`INSERT IGNORE INTO AiAnalysisJob (id, userId, jobType, sessionId, status, queuedAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, 'pending', NOW(), NOW())`,
|
||||
[`ar-dup-1`, TEST_USER, 'active-recall', TEST_SESSION]);
|
||||
// Second enqueue with same jobId — BullMQ ignores duplicate
|
||||
await enqueueJob('ai-analysis', {
|
||||
userId: TEST_USER,
|
||||
type: 'active-recall',
|
||||
questionText: 'Q?', knowledgeItemContent: 'C.', userAnswer: 'A',
|
||||
}, dupJobId).catch(() => {});
|
||||
|
||||
await new Promise(r => setTimeout(r, 8000));
|
||||
await new Promise(r => setTimeout(r, 8_000));
|
||||
|
||||
const after = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
console.log(` AiAnalysisResult: before=${beforeCnt}, after=${Number((after[0] as any).cnt)}`);
|
||||
console.log(` Results: before=${(before[0] as any).cnt} after=${(after[0] as any).cnt}`);
|
||||
}, 15_000);
|
||||
});
|
||||
|
||||
// ── 3. Document Import ──
|
||||
// ── Document Import ──
|
||||
|
||||
describe('Document Import', () => {
|
||||
it('创建 KnowledgeItem', async () => {
|
||||
await dbExec(DB_URL,
|
||||
`INSERT INTO KnowledgeItem (id, userId, knowledgeBaseId, itemType, title, content, status, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`,
|
||||
[`ki-import-1`, TEST_USER, TEST_KB, 'concept', 'Import Test', '# Test\n\nHello world', 'active']);
|
||||
|
||||
const rows = await dbQuery(DB_URL, 'SELECT id FROM KnowledgeItem WHERE id = ?', ['ki-import-1']);
|
||||
expect(rows.length).toBeGreaterThan(0);
|
||||
console.log(` KnowledgeItem ID: ki-import-1`);
|
||||
});
|
||||
it('通过 BullMQ 入队后 KnowledgeItem 可见', async () => {
|
||||
await enqueueJob('document-import', {
|
||||
importId: `test-import-${Date.now()}`,
|
||||
userId: TEST_USER,
|
||||
knowledgeBaseId: TEST_KB,
|
||||
sourceId: `test-source-${Date.now()}`,
|
||||
content: '# Test Document',
|
||||
title: 'Integration Test Doc',
|
||||
type: 'markdown',
|
||||
});
|
||||
|
||||
// ── 4. SIGKILL 恢复 ──
|
||||
await new Promise(r => setTimeout(r, 8_000));
|
||||
|
||||
const rows = await dbQuery(DB_URL,
|
||||
'SELECT id, title FROM KnowledgeItem WHERE userId = ? ORDER BY createdAt DESC', [TEST_USER]);
|
||||
console.log(` KnowledgeItems: ${rows.length}`);
|
||||
for (const r of rows) console.log(` id=${(r as any).id} title=${(r as any).title}`);
|
||||
}, 15_000);
|
||||
});
|
||||
|
||||
// ── SIGKILL 恢复 ──
|
||||
|
||||
describe('SIGKILL 恢复', () => {
|
||||
it('Worker SIGKILL 后重启不产生重复结果', async () => {
|
||||
const before = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
const beforeCnt = Number((before[0] as any).cnt);
|
||||
|
||||
// Kill worker
|
||||
console.log(` Sending SIGKILL to Worker (pid=${getWorkerPid()})...`);
|
||||
await enqueueJob('ai-analysis', {
|
||||
userId: TEST_USER,
|
||||
type: 'active-recall',
|
||||
questionText: 'Test?', knowledgeItemContent: 'Content.', userAnswer: 'Answer',
|
||||
});
|
||||
|
||||
// Wait briefly then kill
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
console.log(` Sending SIGKILL to Worker (pid=${getWorkerPid()})`);
|
||||
await stopWorker('SIGKILL');
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
|
||||
// Restart worker
|
||||
const newWorkerPid = await startWorker(DB_URL, REDIS_HOST, REDIS_PORT, REDIS_PW, `test-worker-recovered-${Date.now()}`);
|
||||
console.log(` Worker restarted, pid=${newWorkerPid}`);
|
||||
await new Promise(r => setTimeout(r, 8_000));
|
||||
// Restart
|
||||
const newPid = await startWorker(DB_URL, REDIS_HOST, String(REDIS_PORT), REDIS_PW, `test-worker-recovered-${Date.now()}`);
|
||||
console.log(` Worker restarted, pid=${newPid}`);
|
||||
await new Promise(r => setTimeout(r, 10_000));
|
||||
|
||||
const after = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
console.log(` AiAnalysisResult: before=${beforeCnt}, after=${Number((after[0] as any).cnt)}`);
|
||||
}, 20_000);
|
||||
console.log(` Results: before=${(before[0] as any).cnt} after=${(after[0] as any).cnt}`);
|
||||
}, 25_000);
|
||||
});
|
||||
|
||||
// ── 5. Provider 故障 ──
|
||||
// ── Provider 故障 ──
|
||||
|
||||
describe('Provider 故障', () => {
|
||||
it('Provider 500 → job failed, 无部分实体', async () => {
|
||||
mockAI.set500();
|
||||
|
||||
const beforeResults = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
|
||||
await dbExec(DB_URL,
|
||||
`INSERT IGNORE INTO AiAnalysisJob (id, userId, jobType, sessionId, status, queuedAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, 'pending', NOW(), NOW())`,
|
||||
[`ar-500-test`, TEST_USER, 'feynman-evaluation', TEST_SESSION]);
|
||||
|
||||
await new Promise(r => setTimeout(r, 10_000));
|
||||
|
||||
const jobRows = await dbQuery(DB_URL,
|
||||
'SELECT status, progress FROM AiAnalysisJob WHERE id = ?', ['ar-500-test']);
|
||||
console.log(` Job status: ${(jobRows[0] as any)?.status}, retries: ${(jobRows[0] as any)?.progress}`);
|
||||
await enqueueJob('ai-analysis', {
|
||||
userId: TEST_USER,
|
||||
type: 'feynman-evaluation',
|
||||
knowledgeItemTitle: 'Test', knowledgeItemContent: 'Content.', userExplanation: 'Explain.',
|
||||
});
|
||||
|
||||
await new Promise(r => setTimeout(r, 15_000));
|
||||
mockAI.setNormal();
|
||||
}, 15_000);
|
||||
|
||||
const afterResults = await dbQuery(DB_URL,
|
||||
'SELECT COUNT(*) as cnt FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
console.log(` Results: before=${(beforeResults[0] as any).cnt} after=${(afterResults[0] as any).cnt}`);
|
||||
}, 25_000);
|
||||
|
||||
it('Provider 非法 JSON → job failed', async () => {
|
||||
mockAI.setInvalidJson();
|
||||
|
||||
await dbExec(DB_URL,
|
||||
`INSERT IGNORE INTO AiAnalysisJob (id, userId, jobType, sessionId, status, queuedAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, 'pending', NOW(), NOW())`,
|
||||
[`ar-badjson-test`, TEST_USER, 'active-recall', TEST_SESSION]);
|
||||
|
||||
await new Promise(r => setTimeout(r, 10_000));
|
||||
|
||||
const jobRows = await dbQuery(DB_URL,
|
||||
'SELECT status FROM AiAnalysisJob WHERE id = ?', ['ar-badjson-test']);
|
||||
console.log(` Job status: ${(jobRows[0] as any)?.status}`);
|
||||
|
||||
mockAI.setNormal();
|
||||
}, 15_000);
|
||||
await enqueueJob('ai-analysis', {
|
||||
userId: TEST_USER,
|
||||
type: 'active-recall',
|
||||
questionText: 'Q?', knowledgeItemContent: 'C.', userAnswer: 'A',
|
||||
});
|
||||
|
||||
// ── 6. ID 追溯汇总 ──
|
||||
await new Promise(r => setTimeout(r, 15_000));
|
||||
mockAI.setNormal();
|
||||
|
||||
const jobs = await dbQuery(DB_URL,
|
||||
'SELECT id, status FROM AiAnalysisJob WHERE userId = ? ORDER BY createdAt DESC LIMIT 1', [TEST_USER]);
|
||||
console.log(` Latest job: id=${(jobs[0] as any)?.id} status=${(jobs[0] as any)?.status}`);
|
||||
}, 25_000);
|
||||
});
|
||||
|
||||
// ── ID 追溯汇总 ──
|
||||
|
||||
describe('ID 追溯汇总', () => {
|
||||
it('输出所有测试产生的 ID', async () => {
|
||||
@ -265,9 +272,6 @@ describe('M-AI-01-09 真实集成测试', () => {
|
||||
console.log(`API PID: ${getApiPid()}`);
|
||||
console.log(`Worker PID: ${getWorkerPid()}`);
|
||||
console.log(`Test User: ${TEST_USER}`);
|
||||
console.log(`Test KB: ${TEST_KB}`);
|
||||
console.log(`Test KI: ${TEST_KI}`);
|
||||
console.log(`Test Session: ${TEST_SESSION}`);
|
||||
|
||||
const jobs = await dbQuery(DB_URL, 'SELECT id, status FROM AiAnalysisJob WHERE userId = ?', [TEST_USER]);
|
||||
console.log(`AiAnalysisJobs: ${jobs.map((r: any) => `${r.id}(${r.status})`).join(', ')}`);
|
||||
@ -275,13 +279,13 @@ describe('M-AI-01-09 真实集成测试', () => {
|
||||
const results = await dbQuery(DB_URL, 'SELECT id, jobId FROM AiAnalysisResult WHERE userId = ?', [TEST_USER]);
|
||||
console.log(`AiAnalysisResults: ${results.map((r: any) => r.id).join(', ')}`);
|
||||
|
||||
const items = await dbQuery(DB_URL, 'SELECT id FROM FocusItem WHERE userId = ?', [TEST_USER]);
|
||||
const items = await dbQuery(DB_URL, 'SELECT id, source FROM FocusItem WHERE userId = ?', [TEST_USER]);
|
||||
console.log(`FocusItems: ${items.map((r: any) => r.id).join(', ')}`);
|
||||
|
||||
const cards = await dbQuery(DB_URL, 'SELECT id FROM ReviewCard WHERE userId = ?', [TEST_USER]);
|
||||
console.log(`ReviewCards: ${cards.map((r: any) => r.id).join(', ')}`);
|
||||
|
||||
const kis = await dbQuery(DB_URL, 'SELECT id FROM KnowledgeItem WHERE userId = ?', [TEST_USER]);
|
||||
const kis = await dbQuery(DB_URL, 'SELECT id, title FROM KnowledgeItem WHERE userId = ?', [TEST_USER]);
|
||||
console.log(`KnowledgeItems: ${kis.map((r: any) => r.id).join(', ')}`);
|
||||
console.log('===============================\n');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user