annotate mrjunejune/test/conversation_api_test.js @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 056790c4fb0d
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
1 const assert = require('node:assert/strict');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
2 const fs = require('node:fs');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
3 const net = require('node:net');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
4 const os = require('node:os');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
5 const path = require('node:path');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
6 const { spawn } = require('node:child_process');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
7
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
8 const RUNFILES = process.env.JS_BINARY__RUNFILES;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
9 const WORKSPACE = process.env.JS_BINARY__WORKSPACE;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
10 const runfilesWorkspace = path.join(RUNFILES, WORKSPACE);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
11
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
12 function findFreePort() {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
13 return new Promise((resolve, reject) => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
14 const socket = net.createServer();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
15 socket.once('error', reject);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
16 socket.listen(0, '127.0.0.1', () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
17 const address = socket.address();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
18 socket.close(error => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
19 if (error) reject(error);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
20 else resolve(String(address.port));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
21 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
22 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
23 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
24 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
25
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
26 async function waitForServer(child, baseUrl, logs) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
27 const deadline = Date.now() + 15000;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
28 while (Date.now() < deadline) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
29 if (child.exitCode !== null) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
30 throw new Error(`Server exited with ${child.exitCode}\n${logs.join('')}`);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
31 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
32 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
33 const response = await fetch(baseUrl);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
34 if (response.ok) return;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
35 } catch {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
36 // Keep waiting for startup.
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
37 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
38 await new Promise(resolve => setTimeout(resolve, 100));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
39 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
40 throw new Error(`Server startup timed out\n${logs.join('')}`);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
41 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
42
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
43 async function stopProcess(child) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
44 if (!child || child.exitCode !== null) return;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
45 child.kill('SIGTERM');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
46 await new Promise(resolve => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
47 const timer = setTimeout(() => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
48 if (child.exitCode === null) child.kill('SIGKILL');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
49 }, 3000);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
50 child.once('exit', () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
51 clearTimeout(timer);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
52 resolve();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
53 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
54 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
55 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
56
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
57 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
58 /* Cookie jar: tracks Set-Cookie headers across responses */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
59 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
60
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
61 class CookieJar {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
62 constructor() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
63 this._cookies = new Map();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
64 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
65
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
66 /** Update jar from a Set-Cookie header value (single directive). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
67 updateFromDirective(directive) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
68 if (!directive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
69 const firstPart = directive.split(';')[0].trim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
70 const eqIdx = firstPart.indexOf('=');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
71 if (eqIdx < 0) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
72 const name = firstPart.slice(0, eqIdx).trim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
73 const value = firstPart.slice(eqIdx + 1).trim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
74 if (!name) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
75 // A max-age of -1 or a "deleted" value clears the cookie.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
76 if (/max-age\s*=\s*-?0/i.test(directive) || value === '' || value.toLowerCase() === 'deleted') {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
77 this._cookies.delete(name);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
78 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
79 this._cookies.set(name, value);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
80 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
81 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
82
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
83 /** Update jar from a response (handles multiple Set-Cookie headers). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
84 updateFromResponse(response) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
85 let setCookies;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
86 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
87 setCookies = response.headers.getSetCookie();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
88 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
89 // Fallback for older Node versions
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
90 const raw = response.headers.get('set-cookie') || '';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
91 setCookies = raw ? raw.split(',\n').map(s => s.trim()).filter(Boolean) : [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
92 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
93 for (const directive of setCookies) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
94 this.updateFromDirective(directive);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
95 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
96 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
97
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
98 get header() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
99 return [...this._cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
100 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
101
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
102 clone() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
103 const jar = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
104 for (const [k, v] of this._cookies) jar._cookies.set(k, v);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
105 return jar;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
106 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
107 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
108
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
109 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
110 /* Session bootstrap: GET /api/auth/session → csrfToken + cookie jar */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
111 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
112
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
113 async function bootstrapSession(baseUrl, jar) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
114 const headers = {};
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
115 if (jar.header) headers['Cookie'] = jar.header;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
116 const response = await fetch(`${baseUrl}/api/auth/session`, { headers });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
117 assert.equal(response.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
118 jar.updateFromResponse(response);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
119 const data = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
120 assert.ok(data.csrfToken, 'session must return csrfToken');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
121 return data.csrfToken;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
122 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
123
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
124 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
125 /* Fetch helper: adds Cookie + X-CSRF-Token headers */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
126 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
127
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
128 async function authedFetch(baseUrl, jar, csrfToken, path, options = {}) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
129 const headers = { ...(options.headers || {}) };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
130 if (jar.header) headers['Cookie'] = jar.header;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
131 if (csrfToken) headers['X-CSRF-Token'] = csrfToken;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
132 const response = await fetch(`${baseUrl}${path}`, { ...options, headers });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
133 jar.updateFromResponse(response);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
134 return response;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
135 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
136
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
137 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
138 /* Main test suite */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
139 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
140
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
141 (async () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
142 assert.ok(RUNFILES);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
143 assert.ok(WORKSPACE);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
144 const serverBinary = path.join(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
145 runfilesWorkspace,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
146 'mrjunejune/mrjunejune_server',
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
147 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
148 const fakeSidecar = path.join(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
149 runfilesWorkspace,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
150 'mrjunejune/test/inference_bridge_fake_sidecar',
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
151 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
152 const tempDirectory = fs.mkdtempSync(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
153 path.join(os.tmpdir(), 'mrjunejune-conversation-api-'),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
154 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
155 const databasePath = path.join(tempDirectory, 'conversations.db');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
156 const port = await findFreePort();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
157 const baseUrl = `http://127.0.0.1:${port}`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
158 const logs = [];
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
159 let server;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
160
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
161 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
162 server = spawn(serverBinary, [], {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
163 cwd: runfilesWorkspace,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
164 env: {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
165 ...process.env,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
166 MRJUNEJUNE_PORT: port,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
167 MRJUNEJUNE_DB_PATH: databasePath,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
168 MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
169 MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
170 MRJUNEJUNE_ALLOW_GUEST_INFERENCE: '1',
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
171 },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
172 stdio: ['ignore', 'pipe', 'pipe'],
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
173 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
174 server.stdout.on('data', chunk => logs.push(chunk.toString()));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
175 server.stderr.on('data', chunk => logs.push(chunk.toString()));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
176 await waitForServer(server, baseUrl, logs);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
177
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
178 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
179 // Inference health check
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
180 // ----------------------------------------------------------------
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
181 let response = await fetch(`${baseUrl}/api/inference/health`);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
182 assert.equal(response.status, 200);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
183
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
184 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
185 // Task 2: mutation without any session returns 401 (no guest created)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
186 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
187 response = await fetch(`${baseUrl}/api/conversations`, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
188 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
189 headers: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
190 'Content-Type': 'application/json',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
191 Origin: baseUrl,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
192 'X-CSRF-Token': 'some-token',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
193 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
194 body: JSON.stringify({ title: 'No session' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
195 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
196 assert.equal(response.status, 401, 'mutation without session must return 401');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
197 // Ensure no Set-Cookie guest header was emitted (no guest row written)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
198 const setCookieNoSession = response.headers.get('set-cookie') || '';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
199 assert.ok(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
200 !setCookieNoSession.includes('mjj_guest'),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
201 'mutation without session must not set guest cookie',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
202 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
203
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
204 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
205 // Guest 1: bootstrap session
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
206 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
207 const jar1 = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
208 const csrf1 = await bootstrapSession(baseUrl, jar1);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
209
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
210 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
211 // CSRF rejection: missing X-CSRF-Token header
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
212 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
213 response = await fetch(`${baseUrl}/api/conversations`, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
214 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
215 headers: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
216 'Content-Type': 'application/json',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
217 Origin: baseUrl,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
218 Cookie: jar1.header,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
219 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
220 body: JSON.stringify({ title: 'Blocked' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
221 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
222 assert.equal(response.status, 403);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
223
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
224 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
225 // CSRF rejection: wrong Origin
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
226 // ----------------------------------------------------------------
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
227 response = await fetch(`${baseUrl}/api/conversations`, {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
228 method: 'POST',
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
229 headers: {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
230 'Content-Type': 'application/json',
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
231 Origin: 'https://attacker.invalid',
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
232 'X-CSRF-Token': csrf1,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
233 Cookie: jar1.header,
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
234 },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
235 body: JSON.stringify({ title: 'Blocked' }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
236 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
237 assert.equal(response.status, 403);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
238
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
239 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
240 // Guest 1 creates a conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
241 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
242 response = await authedFetch(baseUrl, jar1, csrf1, '/api/conversations', {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
243 method: 'POST',
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
244 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
245 body: JSON.stringify({ title: 'First quest' }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
246 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
247 assert.equal(response.status, 201);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
248 const created = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
249 assert.match(created.id, /^[0-9a-f-]{36}$/);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
250
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
251 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
252 // Guest 1 gets the conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
253 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
254 response = await authedFetch(baseUrl, jar1, null, `/api/conversations/${created.id}`);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
255 assert.equal(response.status, 200);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
256 let conversation = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
257 assert.equal(conversation.title, 'First quest');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
258 assert.deepEqual(conversation.turns, []);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
259
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
260 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
261 // Guest 1 renames it
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
262 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
263 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
264 baseUrl, jar1, csrf1, `/api/conversations/${created.id}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
265 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
266 method: 'PATCH',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
267 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
268 body: JSON.stringify({ title: 'Renamed quest' }),
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
269 },
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
270 );
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
271 assert.equal(response.status, 200);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
272
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
273 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
274 // Guest 2: a separate identity
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
275 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
276 const jar2 = new CookieJar(); // fresh — no cookies
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
277 const csrf2 = await bootstrapSession(baseUrl, jar2);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
278
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
279 // Guest 2 cannot see Guest 1's conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
280 response = await authedFetch(baseUrl, jar2, null, `/api/conversations/${created.id}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
281 assert.equal(response.status, 404, 'Guest 2 must not see Guest 1 conversation');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
282
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
283 // Guest 2 cannot rename Guest 1's conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
284 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
285 baseUrl, jar2, csrf2, `/api/conversations/${created.id}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
286 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
287 method: 'PATCH',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
288 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
289 body: JSON.stringify({ title: 'Hijacked' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
290 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
291 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
292 assert.equal(response.status, 404, 'Guest 2 rename must fail');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
293
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
294 // Guest 2 cannot delete Guest 1's conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
295 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
296 baseUrl, jar2, csrf2, `/api/conversations/${created.id}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
297 { method: 'DELETE', headers: { Origin: baseUrl } },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
298 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
299 assert.equal(response.status, 404, 'Guest 2 delete must fail');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
300
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
301 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
302 // Listing: Guest 1 sees their own conversations; Guest 2 sees none
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
303 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
304 response = await authedFetch(baseUrl, jar1, null, '/api/conversations');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
305 assert.equal(response.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
306 let listData = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
307 assert.ok(Array.isArray(listData.conversations));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
308 assert.equal(listData.conversations.length, 1);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
309 assert.equal(listData.conversations[0].id, created.id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
310 assert.equal(listData.conversations[0].title, 'Renamed quest');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
311
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
312 response = await authedFetch(baseUrl, jar2, null, '/api/conversations');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
313 assert.equal(response.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
314 listData = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
315 assert.equal(listData.conversations.length, 0);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
316
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
317 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
318 // Pagination: create more conversations and paginate
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
319 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
320 const ids = [created.id];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
321 for (let i = 0; i < 4; i++) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
322 const r = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
323 baseUrl, jar1, csrf1, '/api/conversations',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
324 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
325 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
326 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
327 body: JSON.stringify({ title: `Chat ${i + 2}` }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
328 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
329 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
330 assert.equal(r.status, 201);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
331 const c = await r.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
332 ids.push(c.id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
333 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
334 // Page 1
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
335 response = await authedFetch(baseUrl, jar1, null, '/api/conversations?limit=3');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
336 assert.equal(response.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
337 const page1 = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
338 assert.equal(page1.conversations.length, 3);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
339 assert.ok(page1.cursor, 'cursor should be present');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
340 // Page 2 using cursor
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
341 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
342 baseUrl, jar1, null, `/api/conversations?cursor=${page1.cursor}&limit=3`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
343 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
344 assert.equal(response.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
345 const page2 = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
346 assert.ok(page2.conversations.length >= 1 && page2.conversations.length <= 2);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
347 // IDs must not overlap between pages
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
348 const page1Ids = new Set(page1.conversations.map(c => c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
349 const page2Ids = page2.conversations.map(c => c.id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
350 for (const id of page2Ids) assert.ok(!page1Ids.has(id), 'page 2 must not repeat page 1 IDs');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
351
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
352 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
353 // Streaming turn (existing mock inference path)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
354 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
355 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
356 baseUrl, jar1, csrf1,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
357 `/api/conversations/${created.id}/turns`,
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
358 {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
359 method: 'POST',
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
360 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
361 body: JSON.stringify({ prompt: 'Hello Epi' }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
362 },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
363 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
364 assert.equal(response.status, 200);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
365 assert.match(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
366 response.headers.get('content-type') || '',
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
367 /^text\/event-stream/,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
368 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
369 const stream = await response.text();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
370 assert.match(stream, /event: turn\.accepted/);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
371 assert.match(stream, /event: tool\.started/);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
372 assert.match(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
373 stream,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
374 /"tool":\{"name":"mock-search","prompt_profile":"public_visitor"\}/,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
375 );
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
376 assert.match(stream, /event: assistant\.delta/);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
377 assert.match(stream, /hello \\"traveler\\"\\\\path/);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
378 assert.match(stream, /event: assistant\.completed/);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
379 assert.match(stream, /event: assistant\.usage/);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
380 assert.match(stream, /event: turn\.done/);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
381
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
382 // Guest 2 cannot start a turn on Guest 1's conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
383 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
384 baseUrl, jar2, csrf2,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
385 `/api/conversations/${created.id}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
386 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
387 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
388 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
389 body: JSON.stringify({ prompt: 'Intrude' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
390 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
391 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
392 assert.equal(response.status, 404, 'Guest 2 must not turn on Guest 1 conversation');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
393
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
394 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
395 // Get after turn
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
396 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
397 response = await authedFetch(baseUrl, jar1, null, `/api/conversations/${created.id}`);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
398 conversation = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
399 assert.equal(conversation.title, 'Renamed quest');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
400 assert.equal(conversation.turns.length, 2);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
401 assert.equal(conversation.turns[0].content, 'Hello Epi');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
402 assert.equal(conversation.turns[1].content, 'hello traveler');
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
403 assert.equal(conversation.turns[1].input_tokens, 7);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
404 assert.equal(conversation.turns[1].output_tokens, 3);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
405
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
406 // Listing should show last_message_preview
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
407 response = await authedFetch(baseUrl, jar1, null, '/api/conversations');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
408 listData = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
409 const listedConv = listData.conversations.find(c => c.id === created.id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
410 assert.ok(listedConv, 'created conv must appear in listing');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
411 assert.ok(typeof listedConv.turn_count === 'number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
412 assert.ok(typeof listedConv.last_message_preview === 'string');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
413
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
414 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
415 // Delete
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
416 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
417 response = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
418 baseUrl, jar1, csrf1, `/api/conversations/${created.id}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
419 { method: 'DELETE', headers: { Origin: baseUrl } },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
420 );
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
421 assert.equal(response.status, 204);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
422 response = await authedFetch(baseUrl, jar1, null, `/api/conversations/${created.id}`);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
423 assert.equal(response.status, 404);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
424
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
425 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
426 // Claim: POST /api/conversations/claim (body-based, ID not in URL)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
427 // Uses a dedicated server with a pre-bootstrapped admin user.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
428 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
429 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
430 const claimPort = await findFreePort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
431 const claimBase = `http://127.0.0.1:${claimPort}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
432 const claimTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claim-test-'));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
433 const claimDb = path.join(claimTmpDir, 'claim.db');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
434 const claimLogs = [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
435 // Pre-computed scrypt hash of "TestPass123!"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
436 const claimHash =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
437 'zenbu-scrypt$v=1$N=32768$r=8$p=1$cd915bb57924094c3c53be9e8e05fcf4$' +
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
438 '15998394fca01b498124ca96c6da4e78f271b1d52043a5344e1a5ee7f92c5332';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
439 const claimServer = spawn(serverBinary, [], {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
440 cwd: runfilesWorkspace,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
441 env: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
442 ...process.env,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
443 MRJUNEJUNE_PORT: claimPort,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
444 MRJUNEJUNE_DB_PATH: claimDb,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
445 MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
446 MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
447 AUTH_BOOTSTRAP_USERNAME: 'claimadmin',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
448 AUTH_BOOTSTRAP_PASSWORD_HASH: claimHash,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
449 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
450 stdio: ['ignore', 'pipe', 'pipe'],
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
451 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
452 claimServer.stdout.on('data', c => claimLogs.push(c.toString()));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
453 claimServer.stderr.on('data', c => claimLogs.push(c.toString()));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
454 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
455 await waitForServer(claimServer, claimBase, claimLogs);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
456
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
457 // Guest creates a conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
458 const guestJarC = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
459 const guestCsrfC = await bootstrapSession(claimBase, guestJarC);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
460 const guestConvR = await authedFetch(claimBase, guestJarC, guestCsrfC, '/api/conversations', {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
461 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
462 headers: { 'Content-Type': 'application/json', Origin: claimBase },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
463 body: JSON.stringify({ title: 'Legacy conv' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
464 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
465 assert.equal(guestConvR.status, 201);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
466 const guestConv = await guestConvR.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
467 const legacyId = guestConv.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
468
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
469 // Old /:id/claim route must be gone (404)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
470 const oldRouteR = await authedFetch(claimBase, guestJarC, guestCsrfC,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
471 `/api/conversations/${legacyId}/claim`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
472 { method: 'POST', headers: { Origin: claimBase } });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
473 assert.ok(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
474 oldRouteR.status === 404 || oldRouteR.status === 405,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
475 `Old claim route must be absent, got ${oldRouteR.status}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
476 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
477
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
478 // Guest claim attempt must be 403
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
479 const guestClaimR = await authedFetch(claimBase, guestJarC, guestCsrfC,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
480 '/api/conversations/claim',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
481 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
482 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
483 headers: { 'Content-Type': 'application/json', Origin: claimBase },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
484 body: JSON.stringify({ conversationId: legacyId }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
485 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
486 assert.equal(guestClaimR.status, 403, 'Guest must not claim');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
487
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
488 // Log in as the bootstrapped admin user
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
489 const userJarC = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
490 const userCsrfC1 = await bootstrapSession(claimBase, userJarC);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
491 const loginR = await authedFetch(claimBase, userJarC, userCsrfC1, '/api/auth/login', {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
492 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
493 headers: { 'Content-Type': 'application/json', Origin: claimBase },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
494 body: JSON.stringify({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
495 username: 'claimadmin',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
496 password: 'TestPass123!',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
497 csrfToken: userCsrfC1,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
498 }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
499 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
500 assert.equal(loginR.status, 200, `Login must succeed, got ${loginR.status}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
501 const userCsrfC2 = await bootstrapSession(claimBase, userJarC);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
502
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
503 const assertTurnProfile = async (jar, csrf, expectedProfile, title) => {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
504 const createResponse = await authedFetch(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
505 claimBase,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
506 jar,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
507 csrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
508 '/api/conversations',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
509 {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
510 method: 'POST',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
511 headers: { 'Content-Type': 'application/json', Origin: claimBase },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
512 body: JSON.stringify({ title }),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
513 },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
514 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
515 assert.equal(createResponse.status, 201);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
516 const createdConversation = await createResponse.json();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
517 const turnResponse = await authedFetch(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
518 claimBase,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
519 jar,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
520 csrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
521 `/api/conversations/${createdConversation.id}/turns`,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
522 {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
523 method: 'POST',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
524 headers: { 'Content-Type': 'application/json', Origin: claimBase },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
525 body: JSON.stringify({ prompt: 'profile check' }),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
526 },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
527 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
528 assert.equal(turnResponse.status, 200);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
529 assert.match(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
530 await turnResponse.text(),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
531 new RegExp(`"prompt_profile":"${expectedProfile}"`),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
532 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
533 };
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
534
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
535 await assertTurnProfile(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
536 userJarC,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
537 userCsrfC2,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
538 'june_admin',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
539 'Admin profile',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
540 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
541
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
542 const createMemberR = await authedFetch(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
543 claimBase,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
544 userJarC,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
545 userCsrfC2,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
546 '/api/admin/users',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
547 {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
548 method: 'POST',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
549 headers: { 'Content-Type': 'application/json', Origin: claimBase },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
550 body: JSON.stringify({
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
551 username: 'invitedfriend',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
552 temporaryPassword: 'InvitedTempPass123!',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
553 role: 'member',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
554 }),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
555 },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
556 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
557 assert.equal(createMemberR.status, 201);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
558
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
559 const memberJar = new CookieJar();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
560 const memberGuestCsrf = await bootstrapSession(claimBase, memberJar);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
561 const memberLoginR = await authedFetch(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
562 claimBase,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
563 memberJar,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
564 memberGuestCsrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
565 '/api/auth/login',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
566 {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
567 method: 'POST',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
568 headers: { 'Content-Type': 'application/json', Origin: claimBase },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
569 body: JSON.stringify({
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
570 username: 'invitedfriend',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
571 password: 'InvitedTempPass123!',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
572 csrfToken: memberGuestCsrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
573 }),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
574 },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
575 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
576 assert.equal(memberLoginR.status, 200);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
577 const memberLoginData = await memberLoginR.json();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
578 assert.equal(memberLoginData.mustChangePassword, true);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
579
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
580 const memberPasswordCsrf = await bootstrapSession(claimBase, memberJar);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
581 const memberPasswordR = await authedFetch(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
582 claimBase,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
583 memberJar,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
584 memberPasswordCsrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
585 '/api/auth/password',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
586 {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
587 method: 'POST',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
588 headers: { 'Content-Type': 'application/json', Origin: claimBase },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
589 body: JSON.stringify({
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
590 currentPassword: 'InvitedTempPass123!',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
591 newPassword: 'InvitedPermanentPass123!',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
592 csrfToken: memberPasswordCsrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
593 }),
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
594 },
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
595 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
596 assert.equal(memberPasswordR.status, 200);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
597 const memberCsrf = await bootstrapSession(claimBase, memberJar);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
598 await assertTurnProfile(
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
599 memberJar,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
600 memberCsrf,
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
601 'invited_friend',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
602 'Invited profile',
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
603 );
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
604
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
605 // A user cannot claim a conversation owned by a different guest.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
606 const claimR = await authedFetch(claimBase, userJarC, userCsrfC2,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
607 '/api/conversations/claim',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
608 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
609 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
610 headers: { 'Content-Type': 'application/json', Origin: claimBase },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
611 body: JSON.stringify({ conversationId: legacyId }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
612 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
613 assert.equal(claimR.status, 409, `Guest-owned claim must fail, got ${claimR.status}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
614
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
615 // The conversation remains isolated from the unrelated user.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
616 const ownedR = await authedFetch(claimBase, userJarC, null, `/api/conversations/${legacyId}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
617 assert.equal(ownedR.status, 404, 'User must not see another guest conversation');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
618
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
619 // Missing conversationId returns 400
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
620 const badR = await authedFetch(claimBase, userJarC, userCsrfC2,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
621 '/api/conversations/claim',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
622 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
623 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
624 headers: { 'Content-Type': 'application/json', Origin: claimBase },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
625 body: JSON.stringify({}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
626 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
627 assert.equal(badR.status, 400, 'Missing ID must return 400');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
628
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
629 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
630 await stopProcess(claimServer);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
631 fs.rmSync(claimTmpDir, { recursive: true, force: true });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
632 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
633 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
634
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
635 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
636 // Guest quota: session endpoint returns quota object for guest
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
637 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
638 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
639 const jarQ = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
640 const csrfQ = await bootstrapSession(baseUrl, jarQ);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
641 // Session endpoint must return a quota object (not null) for guests
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
642 const sessionR = await authedFetch(baseUrl, jarQ, null, '/api/auth/session');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
643 assert.equal(sessionR.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
644 const sessionData = await sessionR.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
645 assert.equal(sessionData.kind, 'guest');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
646 assert.ok(sessionData.quota !== null && typeof sessionData.quota === 'object',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
647 'guest session must have non-null quota');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
648 assert.ok(typeof sessionData.quota.turnsLimit === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
649 'quota.turnsLimit must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
650 assert.ok(typeof sessionData.quota.turnsUsed === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
651 'quota.turnsUsed must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
652 assert.ok(typeof sessionData.quota.turnsRemaining === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
653 'quota.turnsRemaining must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
654 assert.ok(typeof sessionData.quota.outputTokensLimit === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
655 'quota.outputTokensLimit must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
656 assert.ok(typeof sessionData.quota.outputTokensUsed === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
657 'quota.outputTokensUsed must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
658 assert.ok(typeof sessionData.quota.outputTokensReserved === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
659 'quota.outputTokensReserved must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
660 assert.ok(typeof sessionData.quota.outputTokensRemaining === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
661 'quota.outputTokensRemaining must be a number');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
662 assert.ok(typeof sessionData.quota.resetsAt === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
663 'quota.resetsAt must be a number (unix timestamp)');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
664 assert.ok(sessionData.quota.resetsAt > Date.now() / 1000,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
665 'quota.resetsAt must be in the future');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
666 assert.equal(sessionData.quota.turnsUsed, 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
667 'fresh guest must have 0 turns used');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
668 assert.equal(sessionData.quota.turnsLimit, 10,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
669 'default turns limit must be 10');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
670 assert.equal(sessionData.quota.turnsRemaining, 10,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
671 'fresh guest must have full turns remaining');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
672 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
673
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
674 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
675 // Guest quota: quota decrements after a successful turn
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
676 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
677 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
678 const jarQ2 = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
679 const csrfQ2 = await bootstrapSession(baseUrl, jarQ2);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
680
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
681 // Create a conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
682 const convR = await authedFetch(baseUrl, jarQ2, csrfQ2, '/api/conversations', {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
683 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
684 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
685 body: JSON.stringify({ title: 'Quota test' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
686 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
687 assert.equal(convR.status, 201);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
688 const convData = await convR.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
689
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
690 // Do a turn
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
691 const turnR = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
692 baseUrl, jarQ2, csrfQ2,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
693 `/api/conversations/${convData.id}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
694 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
695 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
696 headers: { 'Content-Type': 'application/json', Origin: baseUrl },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
697 body: JSON.stringify({ prompt: 'quota turn' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
698 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
699 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
700 assert.equal(turnR.status, 200, 'quota turn must succeed');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
701 // Drain the stream
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
702 await turnR.text();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
703
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
704 // Session must now show 1 turn used
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
705 const sessR2 = await authedFetch(baseUrl, jarQ2, null, '/api/auth/session');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
706 assert.equal(sessR2.status, 200);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
707 const sessData2 = await sessR2.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
708 assert.ok(sessData2.quota !== null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
709 assert.equal(sessData2.quota.turnsUsed, 1,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
710 'turnsUsed must be 1 after one successful turn');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
711 assert.equal(sessData2.quota.turnsRemaining, 9,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
712 'turnsRemaining must be 9 after one turn');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
713 assert.ok(sessData2.quota.outputTokensUsed > 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
714 'outputTokensUsed must be > 0 after a turn');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
715 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
716
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
717 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
718 // Guest quota: 429 when turns are exhausted (limit=1 via env)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
719 // ----------------------------------------------------------------
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
720 // Note: The main server is started with default limit (10 turns). This test
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
721 // exhausts the remaining turns by doing 9 more turns on a fresh guest, then
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
722 // verifies the next attempt returns 429.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
723 // We use a separate small-limit server for this test to keep the suite fast.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
724 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
725 const portQ = await findFreePort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
726 const tempDirQ = fs.mkdtempSync(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
727 path.join(os.tmpdir(), 'quota-limit-'),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
728 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
729 const dbQ = path.join(tempDirQ, 'q.db');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
730 const logsQ = [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
731 const serverQ = spawn(serverBinary, [], {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
732 cwd: runfilesWorkspace,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
733 env: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
734 ...process.env,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
735 MRJUNEJUNE_PORT: portQ,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
736 MRJUNEJUNE_DB_PATH: dbQ,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
737 MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
738 MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
739 MRJUNEJUNE_ALLOW_GUEST_INFERENCE: '1',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
740 AUTH_GUEST_DAILY_TURNS: '1',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
741 AUTH_GUEST_DAILY_OUTPUT_TOKENS: '10000',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
742 AUTH_GUEST_REQUEST_OUTPUT_TOKENS: '100',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
743 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
744 stdio: ['ignore', 'pipe', 'pipe'],
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
745 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
746 serverQ.stdout.on('data', c => logsQ.push(c.toString()));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
747 serverQ.stderr.on('data', c => logsQ.push(c.toString()));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
748 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
749 await waitForServer(serverQ, `http://127.0.0.1:${portQ}`, logsQ);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
750 const baseQ = `http://127.0.0.1:${portQ}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
751
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
752 const jarL = new CookieJar();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
753 const csrfL = await bootstrapSession(baseQ, jarL);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
754
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
755 // Create a conversation
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
756 const convL = await authedFetch(baseQ, jarL, csrfL, '/api/conversations', {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
757 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
758 headers: { 'Content-Type': 'application/json', Origin: baseQ },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
759 body: JSON.stringify({ title: 'Limit test' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
760 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
761 assert.equal(convL.status, 201);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
762 const convLData = await convL.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
763
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
764 // First turn must succeed (limit=1)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
765 const turn1 = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
766 baseQ, jarL, csrfL,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
767 `/api/conversations/${convLData.id}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
768 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
769 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
770 headers: { 'Content-Type': 'application/json', Origin: baseQ },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
771 body: JSON.stringify({ prompt: 'hello' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
772 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
773 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
774 assert.equal(turn1.status, 200, 'first turn within limit must succeed');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
775 await turn1.text();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
776
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
777 // Create a second conversation for second turn
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
778 const convL2 = await authedFetch(baseQ, jarL, csrfL, '/api/conversations', {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
779 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
780 headers: { 'Content-Type': 'application/json', Origin: baseQ },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
781 body: JSON.stringify({ title: 'Limit test 2' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
782 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
783 assert.equal(convL2.status, 201);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
784 const convL2Data = await convL2.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
785
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
786 // Second turn must be rejected with 429
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
787 const turn2 = await authedFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
788 baseQ, jarL, csrfL,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
789 `/api/conversations/${convL2Data.id}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
790 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
791 method: 'POST',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
792 headers: { 'Content-Type': 'application/json', Origin: baseQ },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
793 body: JSON.stringify({ prompt: 'should fail' }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
794 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
795 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
796 assert.equal(turn2.status, 429, 'second turn must be 429 when turns exhausted');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
797 const errorData = await turn2.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
798 assert.ok(errorData.error, '429 must have error field');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
799 assert.equal(errorData.error.code, 'guest_quota_turns_exhausted',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
800 '429 code must be guest_quota_turns_exhausted');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
801 assert.ok(errorData.error.quota, '429 must include quota details');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
802 assert.equal(errorData.error.quota.turnsRemaining, 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
803 'turnsRemaining must be 0 in 429 payload');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
804 assert.ok(typeof errorData.error.quota.resetsAt === 'number',
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
805 '429 quota must include resetsAt');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
806
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
807 // Verify no turn was persisted for the rejected request
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
808 const convL2Get = await authedFetch(baseQ, jarL, null,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
809 `/api/conversations/${convL2Data.id}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
810 const convL2Detail = await convL2Get.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
811 assert.equal(convL2Detail.turns.length, 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
812 'no turn must be persisted when quota is denied');
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
813
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
814 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
815 await stopProcess(serverQ);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
816 fs.rmSync(tempDirQ, { recursive: true, force: true });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
817 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
818 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 261
diff changeset
819
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
820 } finally {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
821 await stopProcess(server);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
822 fs.rmSync(tempDirectory, { recursive: true, force: true });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
823 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
824 })().catch(error => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
825 console.error(error.stack || error);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
826 process.exitCode = 1;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents:
diff changeset
827 });