test/default-vendor-preferences.test.js
Rama main · réplica de lectura del repositorio original.
var assert = require("assert");
var test = require("node:test");
var preferencesModule = require("../lib/users-default-vendor-preferences");
var serviceModule = require("../lib/server-default-vendor");
var fs = require("fs");
var vm = require("vm");
var path = require("path");
function storage(initial, multiUser, configInitial) {
var data = JSON.parse(JSON.stringify(initial));
var config = Object.assign({}, configInitial || {});
var configModule = {
loadConfig: function () { return Object.assign({}, config); },
saveConfig: function (next) { config = Object.assign({}, next); },
};
var preferences = preferencesModule.attachDefaultVendorPreferences({
loadUsers: function () { return JSON.parse(JSON.stringify(data)); },
saveUsers: function (next) { data = JSON.parse(JSON.stringify(next)); },
isMultiUser: function () { return multiUser; },
configModule: configModule,
});
return { preferences: preferences, read: function () { return data; }, config: function () { return config; } };
}
test("default coding vendor persists in single-user config without dropping settings", function () {
var fixture = storage({ users: [] }, false, { port: 2633, tls: true });
assert.equal(fixture.preferences.setDefaultVendor("default", "codex").ok, true);
assert.deepEqual(fixture.config(), { port: 2633, tls: true, defaultVendorPreference: "codex" });
assert.equal(fixture.preferences.getDefaultVendor("default").vendor, "codex");
assert.equal(fixture.preferences.setDefaultVendor("default", "").ok, true);
assert.deepEqual(fixture.config(), { port: 2633, tls: true });
});
test("clearing a single-user record preference also clears an older config preference", function () {
var fixture = storage({ users: [{ id: "default", defaultVendorPreference: "codex" }] }, false, { defaultVendorPreference: "claude", keep: true });
assert.equal(fixture.preferences.getDefaultVendor("default").vendor, "codex");
assert.equal(fixture.preferences.setDefaultVendor("default", "").ok, true);
assert.equal(fixture.preferences.getDefaultVendor("default").present, false);
assert.equal(fixture.config().defaultVendorPreference, undefined);
assert.equal(fixture.config().keep, true);
});
test("default coding vendor is isolated per multi-user record and rejects invalid values", function () {
var fixture = storage({ users: [{ id: "u1" }, { id: "u2", defaultVendorPreference: "claude" }] }, true);
assert.equal(fixture.preferences.setDefaultVendor("u1", "codex").ok, true);
assert.equal(fixture.preferences.getDefaultVendor("u1").vendor, "codex");
assert.equal(fixture.preferences.getDefaultVendor("u2").vendor, "claude");
assert.equal(fixture.preferences.setDefaultVendor("u1", "not a vendor").ok, false);
assert.equal(fixture.preferences.setDefaultVendor("missing", "codex").ok, false);
});
function serviceFixture() {
var records = { u1: { id: "u1" }, u2: { id: "u2" } };
var saved = {};
var clients = [];
var users = {
isMultiUser: function () { return true; },
findUserById: function (id) { return records[id] || null; },
getDefaultVendorPreference: function (id) { return { present: !!saved[id], vendor: saved[id] || null }; },
setDefaultVendorPreference: function (id, vendor) { saved[id] = vendor; return { ok: true, vendor: vendor }; },
};
var homeChatHandler = { findMateProject: function () { return { ctx: { getVendorModelAvailability: function () { return [{ id: "claude", installed: true }, { id: "codex", installed: true }]; } } }; } };
var service = serviceModule.attachDefaultVendorService({ users: users, homeChatHandler: homeChatHandler, forEachAppClient: function (fn) { clients.forEach(fn); }, serverEpoch: "test-epoch" });
function ws(id) { var socket = { readyState: 1, _clayUser: { id: id }, sent: [] }; socket.send = function (value) { socket.sent.push(JSON.parse(value)); }; return socket; }
return { service: service, ws: ws, clients: clients, saved: saved };
}
test("default vendor service validates installed vendors and broadcasts only to same user", function () {
var fixture = serviceFixture();
var first = fixture.ws("u1");
var sameUser = fixture.ws("u1");
var otherUser = fixture.ws("u2");
fixture.clients.push(sameUser, otherUser);
fixture.service.handleMessage(first, { type: "default_vendor_set", requestId: "save", vendor: "codex" });
assert.equal(fixture.saved.u1, "codex");
assert.equal(sameUser.sent.some(function (message) { return message.preference === "codex"; }), true);
assert.equal(otherUser.sent.some(function (message) { return message.preference === "codex"; }), false);
assert.equal(sameUser.sent.some(function (message) { return Object.prototype.hasOwnProperty.call(message, "projectSlug"); }), false);
fixture.service.handleMessage(first, { type: "default_vendor_set", requestId: "bad", vendor: "kiro" });
assert.match(first.sent[first.sent.length - 1].error, /not installed or authorized/);
});
test("same-user broadcasts omit source project while correlated replies retain it", function () {
var fixture = serviceFixture();
var projectOne = fixture.ws("u1");
var projectTwo = fixture.ws("u1");
fixture.clients.push(projectTwo);
fixture.service.handleMessage(projectOne, { type: "default_vendor_set", requestId: "project-one", vendor: "codex" }, "project-one");
assert.equal(projectTwo.sent[0].projectSlug, undefined);
assert.equal(projectOne.sent[0].projectSlug, "project-one");
assert.equal(projectOne.sent[0].requestId, "project-one");
});
test("default vendor save failure leaves the canonical preference unchanged", function () {
var fixture = serviceFixture();
var ws = fixture.ws("u1");
fixture.saved.u1 = "claude";
fixture.service = serviceModule.attachDefaultVendorService({
users: {
isMultiUser: function () { return true; },
findUserById: function (id) { return id === "u1" ? { id: id } : null; },
getDefaultVendorPreference: function () { return { present: true, vendor: "claude" }; },
setDefaultVendorPreference: function () { return { ok: false, error: "save failed" }; },
},
homeChatHandler: { findMateProject: function () { return { ctx: { getVendorModelAvailability: function () { return [{ id: "codex", installed: true }]; } } }; } },
forEachAppClient: function () {},
serverEpoch: "failure-epoch",
});
fixture.service.handleMessage(ws, { type: "default_vendor_set", requestId: "failure", vendor: "codex" });
assert.equal(ws.sent[0].preference, undefined);
assert.equal(ws.sent[0].error, "save failed");
assert.equal(fixture.saved.u1, "claude");
});
test("default vendor service derives the live actor on availability lookup", function () {
var observed = null;
var fixture = serviceFixture();
fixture.service = serviceModule.attachDefaultVendorService({
users: {
isMultiUser: function () { return true; },
findUserById: function (id) { return id === "u1" ? { id: id, role: "user" } : null; },
getDefaultVendorPreference: function () { return { present: false, vendor: null }; },
setDefaultVendorPreference: function () { return { ok: true }; },
},
homeChatHandler: { findMateProject: function () { return { ctx: { getVendorModelAvailability: function (socket) { observed = socket._clayUser; return [{ id: "codex", installed: true }]; } } }; } },
forEachAppClient: function () {},
serverEpoch: "actor-epoch",
});
var socket = fixture.ws("u1");
socket._clayUser.role = "stale-client-role";
fixture.service.handleMessage(socket, { type: "default_vendor_get", requestId: "fresh" });
assert.deepEqual(observed, { id: "u1", role: "user" });
});
function loadClient() {
var source = fs.readFileSync(path.join(__dirname, "../lib/public/modules/default-vendor.js"), "utf8");
source = source.replace(/^import .*;\n/gm, "").replace(/export function /g, "function ");
source += "\nthis.api={requestDefaultVendor:requestDefaultVendor,beginDefaultVendorConnection:beginDefaultVendorConnection,saveDefaultVendor:saveDefaultVendor,handleDefaultVendorMessage:handleDefaultVendorMessage};";
var state = { connected: true, myUserId: "u1", currentSlug: "project-a", defaultVendorState: { loading: false, saving: false, getRequestId: null, saveRequestId: null, preference: "claude", preferencePresent: true, installedVendors: ["claude", "codex"], accountId: "u1", projectSlug: "project-a", serverEpoch: "old", canonicalRevision: 3, error: "" } };
var listeners = [];
var sent = [];
var context = {
store: { get: function (key) { return state[key]; }, set: function (value) { var previous = state; state = Object.assign({}, state, value); listeners.forEach(function (listener) { listener(state, previous); }); }, subscribe: function (listener) { listeners.push(listener); } },
getWs: function () { return { readyState: 1, send: function (value) { sent.push(JSON.parse(value)); } }; },
firstInstalledVendor: function (installed) { return installed[0] || ""; },
Date: Date,
};
vm.runInNewContext(source, context);
return { api: context.api, getState: function () { return state; }, sent: sent, set: function (value) { context.store.set(value); } };
}
test("client correlates requests, resets account/project boundaries, and ignores stale epochs", function () {
var fixture = loadClient();
fixture.api.requestDefaultVendor();
var requestId = fixture.getState().defaultVendorState.getRequestId;
assert.equal(fixture.getState().defaultVendorState.loading, true);
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: "old", accountId: "u1", projectSlug: "project-a", serverEpoch: "old", preference: "codex", canonicalRevision: 4 });
assert.equal(fixture.getState().defaultVendorState.preference, "claude");
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: requestId, accountId: "u1", projectSlug: "project-a", serverEpoch: "new", preference: "codex", canonicalRevision: 0, ready: true });
assert.equal(fixture.getState().defaultVendorState.preference, "codex");
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: null, accountId: "u1", projectSlug: "project-a", serverEpoch: "old", preference: "claude", canonicalRevision: 8 });
assert.equal(fixture.getState().defaultVendorState.preference, "codex");
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: "null-account", accountId: null, projectSlug: "project-a", error: "Your account is no longer available." });
assert.equal(fixture.getState().defaultVendorState.preference, "codex");
fixture.set({ currentSlug: "project-b" });
assert.equal(fixture.getState().defaultVendorState.preference, null);
assert.equal(fixture.getState().defaultVendorState.projectSlug, "project-b");
});
test("client save failure preserves the prior preference and exposes pending/error state", function () {
var fixture = loadClient();
fixture.api.saveDefaultVendor("codex");
var requestId = fixture.getState().defaultVendorState.saveRequestId;
assert.equal(fixture.getState().defaultVendorState.saving, true);
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: requestId, accountId: "u1", projectSlug: "project-a", serverEpoch: "old", ready: false, error: "save failed" });
assert.equal(fixture.getState().defaultVendorState.preference, "claude");
assert.equal(fixture.getState().defaultVendorState.saving, false);
assert.equal(fixture.getState().defaultVendorState.error, "save failed");
});
test("client keeps a pending save through unrelated broadcasts and accepts correlated null-account failure", function () {
var fixture = loadClient();
fixture.api.saveDefaultVendor("codex");
var requestId = fixture.getState().defaultVendorState.saveRequestId;
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: null, accountId: "u1", serverEpoch: "old", canonicalRevision: 4, preference: "codex", preferencePresent: true });
assert.equal(fixture.getState().defaultVendorState.saving, true);
assert.equal(fixture.getState().defaultVendorState.saveRequestId, requestId);
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: requestId, accountId: null, accountAvailable: false, serverEpoch: "old", ready: false, error: "Your account is no longer available." });
assert.equal(fixture.getState().defaultVendorState.saving, false);
assert.equal(fixture.getState().defaultVendorState.saveRequestId, null);
assert.equal(fixture.getState().defaultVendorState.preference, "codex");
assert.equal(fixture.getState().defaultVendorState.error, "Your account is no longer available.");
});
test("client clears a lost pending save before reconnect GET and accepts the new ACK", function () {
var fixture = loadClient();
fixture.api.saveDefaultVendor("codex");
assert.equal(fixture.getState().defaultVendorState.saving, true);
fixture.api.beginDefaultVendorConnection();
assert.equal(fixture.getState().defaultVendorState.saving, false);
assert.equal(fixture.getState().defaultVendorState.saveRequestId, null);
fixture.api.requestDefaultVendor();
var getRequestId = fixture.getState().defaultVendorState.getRequestId;
fixture.api.handleDefaultVendorMessage({ type: "default_vendor_state", requestId: getRequestId, accountId: "u1", projectSlug: "project-a", serverEpoch: "new", canonicalRevision: 0, preference: "claude", preferencePresent: true, ready: true });
assert.equal(fixture.getState().defaultVendorState.loading, false);
assert.equal(fixture.getState().defaultVendorState.preference, "claude");
});
test("the project sidebar has no account-wide default controls; the inline form uses a project-scoped default", function () {
var desktop = fs.readFileSync(path.join(__dirname, "../lib/public/modules/sidebar-sessions.js"), "utf8");
var mobile = fs.readFileSync(path.join(__dirname, "../lib/public/modules/sidebar-mobile.js"), "utf8");
var form = fs.readFileSync(path.join(__dirname, "../lib/public/modules/session-create-form.js"), "utf8");
[desktop, mobile].forEach(function (source) {
assert.doesNotMatch(source, /saveDefaultVendor|Use automatic default|session-new-set-default|mobile-vendor-set-default/);
});
assert.match(form, /canSetProjectDefault/);
assert.match(form, /session-create-default-btn/);
assert.match(form, /saveProjectDefault\(entry\.id\)/);
assert.match(form, /Set default/);
assert.doesNotMatch(form, /saveDefaultVendor|default_vendor_set/);
});