Remove unused persona marketplace and discover page files.
This commit is contained in:
+40
-21
@@ -127,6 +127,31 @@ test("public health checks reach the backend without exposing diagnostics or req
|
||||
assert.equal((await request(server.baseUrl, "/admin/diagnostics")).status, 403);
|
||||
});
|
||||
|
||||
test("personality API keeps prompts private, validates IDs, and enforces chat ownership", async t => {
|
||||
const server = await startTestServer();
|
||||
t.after(() => server.close());
|
||||
const owner = new CookieJar(), other = new CookieJar();
|
||||
await request(server.baseUrl, "/register", {method:"POST", jar:owner, body:{username:"personality-owner", password:"password123"}});
|
||||
await request(server.baseUrl, "/register", {method:"POST", jar:other, body:{username:"personality-other", password:"password123"}});
|
||||
const listed = await request(server.baseUrl, "/personalities", {jar:owner});
|
||||
assert.equal(listed.json.personalities.length, 6);
|
||||
assert.equal(listed.json.personalities.filter(item => item.isDefault).length, 1);
|
||||
assert.equal(listed.json.personalities.find(item => item.id === "programmer").name, "Ada");
|
||||
assert.equal(listed.json.personalities.find(item => item.id === "programmer").specialization, "Programming & Development");
|
||||
assert.equal(listed.json.personalities.find(item => item.id === "programmer").historicalInspiration, "Ada Lovelace");
|
||||
assert.equal(JSON.stringify(listed.json).includes("systemPrompt"), false);
|
||||
const catalog = await fetch(`${server.baseUrl}/app/personas`, {headers:owner.headers()});
|
||||
assert.match(await catalog.text(), /Personalities/);
|
||||
const created = await request(server.baseUrl, "/chats", {method:"POST", jar:owner, body:{personalityId:"programmer"}});
|
||||
assert.equal(created.json.chat.personality_id, "programmer");
|
||||
const defaultChat = await request(server.baseUrl, "/chats", {method:"POST", jar:owner, body:{systemPrompt:"Injected"}});
|
||||
assert.equal(defaultChat.json.chat.personality_id, "general-assistant");
|
||||
assert.equal((await request(server.baseUrl, "/chats", {method:"POST", jar:owner, body:{personalityId:"invented"}})).status, 400);
|
||||
assert.equal((await request(server.baseUrl, `/chats/${created.json.chat.id}/personality`, {method:"PUT", jar:other, body:{personalityId:"tutor"}})).status, 404);
|
||||
assert.equal((await request(server.baseUrl, `/chats/${created.json.chat.id}/personality`, {method:"PUT", jar:owner, body:{personalityId:"tutor"}})).json.chat.personality_id, "tutor");
|
||||
assert.equal((await request(server.baseUrl, "/personas", {method:"POST", jar:owner, body:{name:"Custom"}})).status, 404);
|
||||
});
|
||||
|
||||
test("new passwords are limited to 72 UTF-8 bytes while legacy credentials remain usable", async t => {
|
||||
const server = await startTestServer({config: {authRateLimitMax: 100}});
|
||||
t.after(() => server.close());
|
||||
@@ -362,8 +387,7 @@ test("username and password changes keep account data and sessions consistent",
|
||||
assert.equal((await request(server.baseUrl, "/login", {method: "POST", body: {username: "accountowner", password: "old-password"}, jar: otherSession})).status, 200);
|
||||
const chat = await request(server.baseUrl, "/chats", {method: "POST", body: {title: "Keep this chat"}, jar});
|
||||
assert.equal(chat.status, 200);
|
||||
const persona = await request(server.baseUrl, "/personas", {method: "POST", body: {name: "Keeper", personaType: "user"}, jar});
|
||||
assert.equal(persona.status, 200);
|
||||
assert.equal((await request(server.baseUrl, "/personas", {method: "POST", body: {name: "Keeper", personaType: "user"}, jar})).status, 404);
|
||||
const db = server.app.locals.db;
|
||||
const attachmentId = "12345678-1234-1234-1234-123456789abc";
|
||||
db.prepare("INSERT INTO attachments (id, username, original_name, storage_key, mime_type, size_bytes, width, height) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
@@ -378,7 +402,6 @@ test("username and password changes keep account data and sessions consistent",
|
||||
assert.equal((await request(server.baseUrl, "/session", {jar})).json.user, "newowner");
|
||||
assert.equal((await request(server.baseUrl, "/session", {jar: otherSession})).json.user, null);
|
||||
assert.equal((await request(server.baseUrl, "/chats", {jar})).json.chats[0].id, chat.json.chat.id);
|
||||
assert.equal((await request(server.baseUrl, "/personas", {jar})).json.userPersonas[0].id, persona.json.persona.id);
|
||||
assert.equal(db.prepare("SELECT username FROM attachments WHERE id = ?").get(attachmentId).username, "newowner");
|
||||
assert.deepEqual(db.pragma("foreign_key_check"), []);
|
||||
|
||||
@@ -770,7 +793,7 @@ test("chat context summary rolls up older messages into future payloads", async
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("roleplay start keeps the session and roleplay messages work", async () => {
|
||||
test.skip("legacy character roleplay endpoints are retired", async () => {
|
||||
const server = await startTestServer();
|
||||
const jar = new CookieJar();
|
||||
|
||||
@@ -808,7 +831,7 @@ test("roleplay start keeps the session and roleplay messages work", async () =>
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("roleplay start degrades to an open chat when opener generation fails", async () => {
|
||||
test.skip("legacy character opener generation is retired", async () => {
|
||||
const modelService = {
|
||||
async generateReply() {
|
||||
throw new Error("Model backend timed out.");
|
||||
@@ -854,7 +877,7 @@ test("roleplay start degrades to an open chat when opener generation fails", asy
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("persona publish and collect flow works across accounts", async () => {
|
||||
test.skip("legacy persona publishing and collection are retired", async () => {
|
||||
const server = await startTestServer();
|
||||
const creatorJar = new CookieJar();
|
||||
const collectorJar = new CookieJar();
|
||||
@@ -1011,7 +1034,7 @@ test("archiving frees an active chat slot and restoring respects the limit", asy
|
||||
} finally { await server.close(); }
|
||||
});
|
||||
|
||||
test("persona clone, version restore, and market feedback endpoints work", async () => {
|
||||
test.skip("legacy persona cloning, versions, and market feedback are retired", async () => {
|
||||
const server = await startTestServer();
|
||||
const jar = new CookieJar();
|
||||
|
||||
@@ -1073,7 +1096,7 @@ test("persona clone, version restore, and market feedback endpoints work", async
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("market listing supports persona type filters, sorting, and favorite state", async () => {
|
||||
test.skip("legacy public persona market is retired", async () => {
|
||||
const server = await startTestServer();
|
||||
const creatorJar = new CookieJar();
|
||||
const fanJar = new CookieJar();
|
||||
@@ -1173,13 +1196,16 @@ test("market listing supports persona type filters, sorting, and favorite state"
|
||||
test("public pages, workspace guards, and legacy chat links have distinct destinations", async () => {
|
||||
const server = await startTestServer();
|
||||
try {
|
||||
for (const [url, marker] of [["/", "A space for every conversation."], ["/login", 'id="accountForm"'], ["/register", 'id="accountForm"'], ["/market", 'id="publicSearch"']]) {
|
||||
for (const [url, marker] of [["/", "A space for every conversation."], ["/login", 'id="accountForm"'], ["/register", 'id="accountForm"']]) {
|
||||
const response = await fetch(server.baseUrl + url);
|
||||
assert.equal(response.status, 200);
|
||||
const html = await response.text();
|
||||
assert.ok(html.includes(marker));
|
||||
assert.ok(!html.includes('id="chat"'));
|
||||
}
|
||||
const retiredMarket = await fetch(server.baseUrl + "/market", {redirect:"manual"});
|
||||
assert.equal(retiredMarket.status, 302);
|
||||
assert.equal(retiredMarket.headers.get("location"), "/login");
|
||||
for (const url of ["/app", "/app/personas", "/app/settings", "/app/chats/42", "/app/chats/new?prompt=hello"]) {
|
||||
const response = await fetch(server.baseUrl + url, {redirect: "manual"});
|
||||
assert.equal(response.status, 302);
|
||||
@@ -1203,20 +1229,13 @@ test("public pages, workspace guards, and legacy chat links have distinct destin
|
||||
} finally { await server.close(); }
|
||||
});
|
||||
|
||||
test("public discovery only exposes published persona summaries", async () => {
|
||||
test("legacy persona creation, publishing, and public discovery are unavailable", async () => {
|
||||
const server = await startTestServer();
|
||||
try {
|
||||
const jar = new CookieJar();
|
||||
await request(server.baseUrl, "/register", {method: "POST", body: {username: "publicmaker", password: "password123"}, jar});
|
||||
const created = await request(server.baseUrl, "/personas", {method: "POST", body: {name: "Rainkeeper", background: "A guide to a rainy city", details: "Private implementation detail", personaType: "assistant"}, jar});
|
||||
const id = created.json.persona.id;
|
||||
assert.equal((await request(server.baseUrl, "/api/discover")).json.personas.length, 0);
|
||||
await request(server.baseUrl, `/personas/${id}/publish`, {method: "POST", body: {}, jar});
|
||||
const result = await request(server.baseUrl, "/api/discover");
|
||||
assert.equal(result.status, 200);
|
||||
assert.equal(result.json.personas[0].name, "Rainkeeper");
|
||||
assert.equal(result.json.personas[0].description, "A guide to a rainy city");
|
||||
assert.deepEqual(Object.keys(result.json.personas[0]).sort(), ["creator_username", "description", "id", "name", "persona_type"]);
|
||||
assert.equal((await request(server.baseUrl, "/personas", {method: "POST", body: {name: "Rainkeeper"}, jar})).status, 404);
|
||||
assert.equal((await request(server.baseUrl, "/api/discover", {jar})).status, 404);
|
||||
} finally { await server.close(); }
|
||||
});
|
||||
|
||||
@@ -1227,7 +1246,7 @@ test("page headers share navigation and keep session markup private", async () =
|
||||
const username = '<maker&friend>';
|
||||
await request(server.baseUrl, "/register", {method: "POST", body: {username, password: "password123"}, jar});
|
||||
let sharedNavigation;
|
||||
for (const route of ["/", "/app", "/app/chats/new", "/app/personas", "/market", "/app/settings", "/privacy.html", "/tos.html"]) {
|
||||
for (const route of ["/", "/app", "/app/chats/new", "/app/personas", "/app/settings", "/privacy.html", "/tos.html"]) {
|
||||
const response = await fetch(server.baseUrl + route, {headers: jar.headers()});
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.headers.get("cache-control"), "private, no-store");
|
||||
@@ -1381,7 +1400,7 @@ test("browser sanitizer is served from the npm dependency without exposing node_
|
||||
assert.ok((await fetch(server.baseUrl + "/node_modules/dompurify/package.json")).status >= 400);
|
||||
});
|
||||
|
||||
test("persona corrections explicitly replace provisional SSE output before persisting the final answer", async t => {
|
||||
test.skip("legacy character reply guards are retired", async t => {
|
||||
let pass = 0;
|
||||
const modelService = {
|
||||
async streamReply(_model, _messages, onChunk, options) {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {createApp} from "../src/app.js";
|
||||
import {DEFAULT_PERSONALITY_ID, getPersonality, listPersonalities} from "../src/services/personalityRegistry.js";
|
||||
import {runMigrations} from "../src/db/schema.js";
|
||||
import {createChatService} from "../src/services/chatService.js";
|
||||
import {createModelService} from "../src/services/modelService.js";
|
||||
|
||||
test("personality metadata is curated and never exposes system prompts", () => {
|
||||
const personalities = listPersonalities();
|
||||
assert.equal(personalities.length, 6);
|
||||
assert.equal(personalities.filter(item => item.isDefault).length, 1);
|
||||
assert.equal(personalities.find(item => item.isDefault).id, DEFAULT_PERSONALITY_ID);
|
||||
assert.deepEqual(personalities.map(item => item.name), ["Leonardo", "Shakespeare", "Ada", "Curie", "Socrates", "Turing"]);
|
||||
for (const item of personalities) {
|
||||
assert.ok(item.name && item.specialization && item.description && item.longDescription);
|
||||
assert.ok(item.historicalInspiration && item.icon && item.accentColor);
|
||||
assert.equal("systemPrompt" in item, false);
|
||||
}
|
||||
assert.match(getPersonality("creative-writer").systemPrompt, /narrative cohesion/);
|
||||
assert.match(getPersonality("creative-writer").systemPrompt, /do not default to archaic language/);
|
||||
assert.match(getPersonality("tutor").systemPrompt, /do not turn every exchange into a quiz or answer only with questions/);
|
||||
assert.match(getPersonality("programmer").systemPrompt, /smallest complete change/);
|
||||
});
|
||||
|
||||
test("chat prompt uses its stored personality and persists personality selection", async t => {
|
||||
const app = createApp({config:{dbPath:":memory:", testMode:true, eventLoopLagMonitorEnabled:false}});
|
||||
t.after(() => app.locals.close());
|
||||
const repositories = app.locals.repositories;
|
||||
app.locals.db.prepare("INSERT INTO users(username,password) VALUES(?,?)").run("writer", "unused");
|
||||
const chat = repositories.createChat("writer", "Draft");
|
||||
repositories.updateChatPersonality(chat.id, "writer", "creative-writer");
|
||||
const restored = repositories.getChat(chat.id, "writer");
|
||||
assert.equal(restored.personality_id, "creative-writer");
|
||||
const chatService = createChatService(repositories, {generateReply:async()=>"", streamReply:async()=>""}, app.locals.config);
|
||||
const payload = chatService.buildMessagesPayload({user:"writer", chatId:chat.id, session:restored, message:"Continue"});
|
||||
assert.match(payload.messagesPayload[0].content, /specialized creative-writing partner/);
|
||||
assert.doesNotMatch(payload.messagesPayload[0].content, /general assistant/i);
|
||||
assert.equal(payload.messagesPayload.at(-1).role, "user");
|
||||
assert.equal(payload.messagesPayload.at(-1).content, "Continue");
|
||||
let sent;
|
||||
const ollama = createModelService({modelApiBaseUrl:"http://ollama.test", modelRequestTimeoutMs:1000, modelUnloadAfterMs:1000}, {
|
||||
fetch:async (_url, options) => {
|
||||
sent = JSON.parse(options.body);
|
||||
return new Response('{"message":{"content":"A scene."},"done":true}\n', {status:200});
|
||||
}
|
||||
});
|
||||
await ollama.generateReply("configured:model", payload.messagesPayload);
|
||||
assert.match(sent.messages.find(item => item.role === "system" && item.content.includes("specialized creative-writing partner")).content, /Follow the user's genre/);
|
||||
assert.equal(sent.messages.at(-1).content, "Continue");
|
||||
});
|
||||
|
||||
test("legacy conversations migrate to General Assistant without changing messages", async t => {
|
||||
const app = createApp({config:{dbPath:":memory:", testMode:true, eventLoopLagMonitorEnabled:false}});
|
||||
t.after(() => app.locals.close());
|
||||
const db = app.locals.db;
|
||||
db.prepare("INSERT INTO users(username,password) VALUES(?,?)").run("legacy", "unused");
|
||||
const chatId = db.prepare("INSERT INTO chat_sessions(username,title) VALUES(?,?)").run("legacy", "Old chat").lastInsertRowid;
|
||||
db.prepare("INSERT INTO chat_messages(chat_id,role,content) VALUES(?,?,?)").run(chatId, "user", "Keep me");
|
||||
db.exec("ALTER TABLE chat_sessions DROP COLUMN personality_id");
|
||||
runMigrations(db);
|
||||
assert.equal(db.prepare("SELECT personality_id FROM chat_sessions WHERE id=?").get(chatId).personality_id, "general-assistant");
|
||||
assert.equal(db.prepare("SELECT content FROM chat_messages WHERE chat_id=?").get(chatId).content, "Keep me");
|
||||
});
|
||||
@@ -321,11 +321,11 @@ test("chat search integrates with streaming, persistence, personas, settings, re
|
||||
|
||||
failSearch = false;
|
||||
emptySearch = false;
|
||||
const persona = (await api("/personas", { personaType: "assistant", name: "ResearchGuide", details: "Patient researcher" })).body.persona;
|
||||
const personaChat = (await api("/chats", { assistantPersonaId: persona.id })).body.chat;
|
||||
assert.equal((await api("/personas", { personaType: "assistant", name: "ResearchGuide" })).status, 404);
|
||||
const personaChat = (await api("/chats", { personalityId: "researcher" })).body.chat;
|
||||
assert.equal((await api("/chat", { chatId: personaChat.id, model: "persona:model", message: "news", webSearch: true })).status, 200);
|
||||
assert.equal(calls.at(-1).model, "persona:model");
|
||||
assert.ok(calls.at(-1).messages.some((message) => message.content.includes("ResearchGuide")));
|
||||
assert.ok(calls.at(-1).messages.some((message) => message.content.includes("research specialist")));
|
||||
assert.ok(calls.at(-1).messages.some((message) => message.content.includes("WEB SEARCH GROUNDING")));
|
||||
|
||||
// A separate app proves missing configuration does not prevent startup or normal chat.
|
||||
|
||||
Reference in New Issue
Block a user