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/); });