fix: salvage EJClaw runtime hardening
- add dist freshness guard to deploy flow - preserve message source provenance for IPC injection - harden Codex usage fallback and workspace package manager detection - document owner branch return protocol
This commit is contained in:
157
src/db-message-source.test.ts
Normal file
157
src/db-message-source.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Database } from 'bun:sqlite';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
_initTestDatabase,
|
||||
_initTestDatabaseFromFile,
|
||||
getMessagesSinceSeq,
|
||||
getRecentChatMessages,
|
||||
storeChatMetadata,
|
||||
storeMessage,
|
||||
} from './db.js';
|
||||
|
||||
describe('message_source_kind persistence', () => {
|
||||
let tempDir: string | null = null;
|
||||
|
||||
beforeEach(() => {
|
||||
_initTestDatabase();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (tempDir) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
tempDir = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('persists explicit message source kind through store and read paths', () => {
|
||||
storeChatMetadata('room@g.us', '2026-04-24T00:00:00.000Z', 'Room');
|
||||
storeMessage({
|
||||
id: 'ipc-1',
|
||||
chat_jid: 'room@g.us',
|
||||
sender: 'hermes',
|
||||
sender_name: 'Hermes',
|
||||
content: 'wake up',
|
||||
timestamp: '2026-04-24T00:00:01.000Z',
|
||||
is_from_me: false,
|
||||
is_bot_message: false,
|
||||
message_source_kind: 'trusted_external_bot',
|
||||
});
|
||||
|
||||
const [recent] = getRecentChatMessages('room@g.us', 1);
|
||||
expect(recent).toMatchObject({
|
||||
id: 'ipc-1',
|
||||
is_bot_message: false,
|
||||
message_source_kind: 'trusted_external_bot',
|
||||
});
|
||||
|
||||
storeMessage({
|
||||
id: 'ipc-1',
|
||||
chat_jid: 'room@g.us',
|
||||
sender: 'hermes',
|
||||
sender_name: 'Hermes',
|
||||
content: 'updated',
|
||||
timestamp: '2026-04-24T00:00:02.000Z',
|
||||
is_from_me: false,
|
||||
is_bot_message: true,
|
||||
message_source_kind: 'ipc_injected_bot',
|
||||
});
|
||||
|
||||
const [updated] = getMessagesSinceSeq('room@g.us', 0, 'EJClaw', 10);
|
||||
expect(updated).toMatchObject({
|
||||
id: 'ipc-1',
|
||||
content: 'updated',
|
||||
is_bot_message: true,
|
||||
message_source_kind: 'ipc_injected_bot',
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults missing message source kind from is_bot_message', () => {
|
||||
storeChatMetadata('room@g.us', '2026-04-24T00:00:00.000Z', 'Room');
|
||||
storeMessage({
|
||||
id: 'human-1',
|
||||
chat_jid: 'room@g.us',
|
||||
sender: 'user',
|
||||
sender_name: 'User',
|
||||
content: 'hello',
|
||||
timestamp: '2026-04-24T00:00:01.000Z',
|
||||
is_from_me: false,
|
||||
is_bot_message: false,
|
||||
});
|
||||
storeMessage({
|
||||
id: 'bot-1',
|
||||
chat_jid: 'room@g.us',
|
||||
sender: 'bot',
|
||||
sender_name: 'Bot',
|
||||
content: 'done',
|
||||
timestamp: '2026-04-24T00:00:02.000Z',
|
||||
is_from_me: false,
|
||||
is_bot_message: true,
|
||||
});
|
||||
|
||||
expect(getRecentChatMessages('room@g.us', 2)).toEqual([
|
||||
expect.objectContaining({ id: 'human-1', message_source_kind: 'human' }),
|
||||
expect.objectContaining({ id: 'bot-1', message_source_kind: 'bot' }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('migrates legacy message tables and backfills source kind', () => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-msg-source-'));
|
||||
const dbPath = path.join(tempDir, 'messages.db');
|
||||
const legacyDb = new Database(dbPath);
|
||||
legacyDb.exec(`
|
||||
CREATE TABLE chats (
|
||||
jid TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
last_message_time TEXT,
|
||||
channel TEXT,
|
||||
is_group INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE TABLE messages (
|
||||
id TEXT,
|
||||
chat_jid TEXT,
|
||||
sender TEXT,
|
||||
sender_name TEXT,
|
||||
content TEXT,
|
||||
timestamp TEXT,
|
||||
seq INTEGER,
|
||||
is_from_me INTEGER,
|
||||
is_bot_message INTEGER DEFAULT 0,
|
||||
PRIMARY KEY (id, chat_jid)
|
||||
);
|
||||
CREATE TABLE schema_migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
INSERT INTO chats (jid, name, last_message_time, channel, is_group)
|
||||
VALUES ('room@g.us', 'Room', '2026-04-24T00:00:02.000Z', 'discord', 1);
|
||||
INSERT INTO messages (
|
||||
id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message
|
||||
) VALUES
|
||||
('human-legacy', 'room@g.us', 'user', 'User', 'hello', '2026-04-24T00:00:01.000Z', 1, 0, 0),
|
||||
('bot-legacy', 'room@g.us', 'bot', 'Bot', 'done', '2026-04-24T00:00:02.000Z', 2, 0, 1);
|
||||
`);
|
||||
const insertMigration = legacyDb.prepare(
|
||||
'INSERT INTO schema_migrations (version, name) VALUES (?, ?)',
|
||||
);
|
||||
for (let version = 1; version <= 12; version += 1) {
|
||||
insertMigration.run(version, `legacy-${version}`);
|
||||
}
|
||||
legacyDb.close();
|
||||
|
||||
_initTestDatabaseFromFile(dbPath);
|
||||
|
||||
expect(getRecentChatMessages('room@g.us', 2)).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'human-legacy',
|
||||
message_source_kind: 'human',
|
||||
}),
|
||||
expect.objectContaining({ id: 'bot-legacy', message_source_kind: 'bot' }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user