Files
zeekayandHanzo Dev 8e77d7828c e2e: a credential-free mock harness, so the suite can run without a live stack
The second half of the same uncommitted 2026-07-27 session. It lets the
playwright suite run against fakes instead of a real deployment: an in-process
fake model (setup/fake-model.js), fake MCP servers over stdio and HTTP, a
tool-approval hook, mock users and fixtures, and specs/mock/ that exercise them.
playwright.config.mock.ts wires it together and is the entry point.

.generated/chat.e2e.yaml is committed deliberately despite the directory name:
it is a credential-free TEMPLATE that playwright.config.mock.ts copies to an
ignored runtime path, and the fake model overrides the endpoint every run, so
the placeholder baseURL in it is never contacted. It is input to the harness,
not output of it — the directory is misleadingly named, which is worth knowing
before someone adds it to .gitignore and breaks the mock run.

packages/api/src/agents/testHook.ts rides along: it is test-only infrastructure
from the same work. Nothing in tracked source imports it (the reference in
packages/api/dist/ is from a stale build, and the tracked agents/index.ts does
not export it), so it is inert until something opts in.

Not run here: this checkout cannot execute the suite — @hanzochat/data-provider
is not linked into node_modules, which already breaks tracked tests. Committed
so the work stops living only on one disk; whoever has a complete workspace can
exercise it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-03 08:26:06 -07:00

92 lines
2.9 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { MongoClient } from 'mongodb';
import type { Db } from 'mongodb';
const DEFAULT_MONGO_URI = 'mongodb://127.0.0.1:27017/Chat-e2e';
/**
* e2e/setup/start-server.js writes the active Mongo URI (memory-Mongo port included)
* here on boot. It honors `E2E_RUNTIME_ENV_PATH`, so resolve the same override the
* server used before falling back to the default location.
*/
function getRuntimeEnvPath(): string {
return (
process.env.E2E_RUNTIME_ENV_PATH ?? path.resolve(__dirname, '../.test-results/runtime-env.json')
);
}
function getMongoUri(): string {
try {
const env = JSON.parse(fs.readFileSync(getRuntimeEnvPath(), 'utf8')) as { MONGO_URI?: string };
if (env.MONGO_URI) {
return env.MONGO_URI;
}
} catch {
/* fall through to env/default */
}
return process.env.MONGO_URI ?? DEFAULT_MONGO_URI;
}
async function resolveUserId(db: Db, userEmail: string): Promise<string> {
const user = await db.collection('users').findOne({ email: userEmail });
if (!user) {
throw new Error(`E2E seed: user "${userEmail}" not found`);
}
return user._id.toString();
}
/** Connect to the e2e MongoDB, run `fn`, and always close the client. */
export async function withMongo<T>(fn: (db: Db) => Promise<T>): Promise<T> {
const client = new MongoClient(getMongoUri());
await client.connect();
try {
return await fn(client.db());
} finally {
await client.close();
}
}
export interface SeedConvo {
conversationId: string;
title: string;
/** Drives the sidebar date group ("Today", "Previous 7 days", ...). */
updatedAt: Date;
}
/**
* Inserts conversation documents directly (bypassing mongoose timestamps) so their
* `updatedAt` can be backdated into specific sidebar date groups.
*/
export async function seedConversations(userEmail: string, convos: SeedConvo[]): Promise<void> {
await withMongo(async (db) => {
const userId = await resolveUserId(db, userEmail);
const docs = convos.map((convo) => ({
conversationId: convo.conversationId,
title: convo.title,
user: userId,
endpoint: 'openAI',
isArchived: false,
createdAt: convo.updatedAt,
updatedAt: convo.updatedAt,
__v: 0,
}));
await db.collection('conversations').insertMany(docs);
});
}
export async function deleteConversations(conversationIds: string[]): Promise<void> {
await withMongo(async (db) => {
await db.collection('conversations').deleteMany({ conversationId: { $in: conversationIds } });
});
}
/** Clears every conversation for the user so the seeded date groups are not pushed
* below the virtualized viewport by rows left behind by other specs. */
export async function clearUserConversations(userEmail: string): Promise<void> {
await withMongo(async (db) => {
const userId = await resolveUserId(db, userEmail);
await db.collection('conversations').deleteMany({ user: userId });
});
}