Files
openchamber/packages/web/server/lib/opencode/routes-upgrade.test.js
T
𝖎𝖚𝖑𝖎𝖎𝖆andBohdan Triapitsyn c88dd16d2a fix: prevent bundled OpenCode self-upgrades (#2525)
* fix: prevent bundled OpenCode self-upgrades

* feat(vscode): support OpenCode upgrades

* fix: refresh OpenCode update status on runtime switch

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-29 19:59:41 +03:00

114 lines
3.2 KiB
JavaScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import express from 'express';
import request from 'supertest';
import { registerOpenCodeRoutes } from './routes.js';
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
const createApp = (overrides = {}) => {
const app = express();
app.use(express.json());
const dependencies = {
getOpenCodeUpgradeCapability: () => ({
supported: false,
manager: 'openchamber',
reason: 'bundled',
}),
buildOpenCodeUrl: (pathname) => `http://127.0.0.1:4096${pathname}`,
getOpenCodeAuthHeaders: () => ({}),
refreshOpenCodeAfterConfigChange: vi.fn(async () => {}),
...overrides,
};
registerOpenCodeRoutes(app, dependencies);
return { app, dependencies };
};
describe('OpenCode upgrade routes', () => {
it('fails closed without contacting the bundled OpenCode updater', async () => {
globalThis.fetch = vi.fn();
const { app } = createApp();
await request(app)
.post('/api/opencode/upgrade')
.send({})
.expect(409, {
success: false,
code: 'OPENCODE_UPGRADE_MANAGED_BY_OPENCHAMBER',
error: 'OpenCode is bundled with OpenChamber Desktop and updates with the app.',
});
expect(globalThis.fetch).not.toHaveBeenCalled();
});
it('reports bundled update ownership through the capability contract', async () => {
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({ healthy: true, version: '1.18.8' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}));
const { app } = createApp();
const response = await request(app)
.get('/api/opencode/upgrade-status')
.expect(200);
expect(response.body).toEqual({
available: false,
currentVersion: '1.18.8',
latestVersion: null,
upgrade: {
supported: false,
manager: 'openchamber',
reason: 'bundled',
},
});
});
it('serializes supported upgrades and preserves the in-flight lock', async () => {
let releaseUpgrade;
const upstreamResponse = new Promise((resolve) => {
releaseUpgrade = () => resolve(new Response(JSON.stringify({ success: true, version: '1.18.9' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}));
});
globalThis.fetch = vi.fn(() => upstreamResponse);
const { app, dependencies } = createApp({
getOpenCodeUpgradeCapability: () => ({
supported: true,
manager: 'opencode',
reason: null,
}),
});
const first = request(app)
.post('/api/opencode/upgrade')
.send({})
.expect(200, {
success: true,
version: '1.18.9',
restarted: true,
})
.then((response) => response);
await vi.waitFor(() => {
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
});
await request(app)
.post('/api/opencode/upgrade')
.send({})
.expect(409, {
success: false,
code: 'OPENCODE_UPGRADE_IN_PROGRESS',
error: 'An OpenCode upgrade is already in progress.',
});
releaseUpgrade();
await first;
expect(dependencies.refreshOpenCodeAfterConfigChange).toHaveBeenCalledTimes(1);
});
});