agent-nuvira 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +17 -3
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/app.js +26 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +108 -2
- package/dist/cli/chat.js.map +1 -1
- package/dist/config/auth.js +18 -0
- package/dist/config/jwt.js +24 -0
- package/dist/config/keys.js +14 -0
- package/dist/gateway/chat-store.d.ts +32 -5
- package/dist/gateway/chat-store.d.ts.map +1 -1
- package/dist/gateway/chat-store.js +52 -6
- package/dist/gateway/chat-store.js.map +1 -1
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +54 -10
- package/dist/gateway/registry.js.map +1 -1
- package/dist/inference/model-validator.d.ts.map +1 -1
- package/dist/inference/model-validator.js +11 -1
- package/dist/inference/model-validator.js.map +1 -1
- package/dist/inference/tool-call-utils.d.ts +31 -0
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +41 -0
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +5 -3
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +2 -2
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/middleware/auth.js +30 -0
- package/dist/nlu/conversation-gate.js +1 -1
- package/dist/nlu/conversation-gate.js.map +1 -1
- package/dist/nlu/entities.d.ts.map +1 -1
- package/dist/nlu/entities.js +12 -0
- package/dist/nlu/entities.js.map +1 -1
- package/dist/nlu/intent.d.ts.map +1 -1
- package/dist/nlu/intent.js +83 -15
- package/dist/nlu/intent.js.map +1 -1
- package/dist/passport.js +34 -0
- package/dist/routes/auth.js +42 -0
- package/dist/routes/user.js +31 -0
- package/dist/server.js +17 -0
- package/dist/tools/pipeline-tool.d.ts.map +1 -1
- package/dist/tools/pipeline-tool.js +14 -0
- package/dist/tools/pipeline-tool.js.map +1 -1
- package/dist/tools/release-sync.d.ts +52 -16
- package/dist/tools/release-sync.d.ts.map +1 -1
- package/dist/tools/release-sync.js +143 -79
- package/dist/tools/release-sync.js.map +1 -1
- package/dist/web-dashboard/src/App.js +85 -0
- package/dist/web-dashboard/src/admin-auth.test.js +186 -0
- package/dist/web-dashboard/src/ansi.js +23 -0
- package/dist/web-dashboard/src/ansi.test.js +31 -0
- package/dist/web-dashboard/src/api-admin-auth.test.js +172 -0
- package/dist/web-dashboard/src/api-admin.test.js +65 -0
- package/dist/web-dashboard/src/api-hub.test.js +117 -0
- package/dist/web-dashboard/src/api.js +1421 -0
- package/dist/web-dashboard/src/api.test.js +51 -0
- package/dist/web-dashboard/src/artifacts.js +128 -0
- package/dist/web-dashboard/src/artifacts.test.js +139 -0
- package/dist/web-dashboard/src/components/AdminPanel.js +567 -0
- package/dist/web-dashboard/src/components/AdminPanel.test.js +288 -0
- package/dist/web-dashboard/src/components/AgentHub.js +1580 -0
- package/dist/web-dashboard/src/components/AgentHub.test.js +343 -0
- package/dist/web-dashboard/src/components/BedrockOnboarding.js +320 -0
- package/dist/web-dashboard/src/components/BenchmarkCharts.js +228 -0
- package/dist/web-dashboard/src/components/ChatPage.js +1586 -0
- package/dist/web-dashboard/src/components/ChatPage.test.js +899 -0
- package/dist/web-dashboard/src/components/ContactsPage.js +141 -0
- package/dist/web-dashboard/src/components/CostDashboard.js +105 -0
- package/dist/web-dashboard/src/components/DAGView.js +477 -0
- package/dist/web-dashboard/src/components/DAGView.test.js +147 -0
- package/dist/web-dashboard/src/components/EnvVarEditor.js +138 -0
- package/dist/web-dashboard/src/components/EvalsPage.js +73 -0
- package/dist/web-dashboard/src/components/EvalsPage.test.js +120 -0
- package/dist/web-dashboard/src/components/ExecutionHistory.js +201 -0
- package/dist/web-dashboard/src/components/GatewayPage.js +40 -0
- package/dist/web-dashboard/src/components/GatewayPage.test.js +74 -0
- package/dist/web-dashboard/src/components/HealthPanel.js +68 -0
- package/dist/web-dashboard/src/components/HistoryBrowser.js +34 -0
- package/dist/web-dashboard/src/components/Layout.js +50 -0
- package/dist/web-dashboard/src/components/Markdown.js +152 -0
- package/dist/web-dashboard/src/components/Markdown.test.js +126 -0
- package/dist/web-dashboard/src/components/MarkdownZeroDep.js +237 -0
- package/dist/web-dashboard/src/components/MemoryPanel.js +81 -0
- package/dist/web-dashboard/src/components/ModelTimeline.js +298 -0
- package/dist/web-dashboard/src/components/ModelsPanel.js +1484 -0
- package/dist/web-dashboard/src/components/ModelsPanel.test.js +460 -0
- package/dist/web-dashboard/src/components/Overview.js +123 -0
- package/dist/web-dashboard/src/components/PhaseTimeline.js +359 -0
- package/dist/web-dashboard/src/components/PhaseTimeline.test.js +234 -0
- package/dist/web-dashboard/src/components/PlatformConfigSection.js +384 -0
- package/dist/web-dashboard/src/components/PlatformConfigSection.test.js +106 -0
- package/dist/web-dashboard/src/components/PlatformsPage.js +250 -0
- package/dist/web-dashboard/src/components/QuotaPanel.js +159 -0
- package/dist/web-dashboard/src/components/QuotaPanel.test.js +85 -0
- package/dist/web-dashboard/src/components/RequestsPanel.js +229 -0
- package/dist/web-dashboard/src/components/RequestsPanel.test.js +103 -0
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.js +938 -0
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.test.js +339 -0
- package/dist/web-dashboard/src/components/RoutingWalkthrough.js +408 -0
- package/dist/web-dashboard/src/components/RoutingWalkthrough.test.js +209 -0
- package/dist/web-dashboard/src/components/TaskConsole.js +119 -0
- package/dist/web-dashboard/src/components/TaskConsole.test.js +123 -0
- package/dist/web-dashboard/src/components/TasksPage.js +256 -0
- package/dist/web-dashboard/src/components/TasksPage.test.js +103 -0
- package/dist/web-dashboard/src/components/TracePanel.js +306 -0
- package/dist/web-dashboard/src/components/WhatsAppPanel.js +195 -0
- package/dist/web-dashboard/src/components/WhatsAppPanel.test.js +122 -0
- package/dist/web-dashboard/src/jsonOrNull.js +37 -0
- package/dist/web-dashboard/src/main.js +15 -0
- package/dist/web-dashboard/src/mask.js +11 -0
- package/dist/web-dashboard/vite.config.js +23 -0
- package/dist/web-dashboard/vitest.config.js +15 -0
- package/package.json +5 -3
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
// (token persistence uses localStorage — jsdom; the file-extension default is node)
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
/**
|
|
6
|
+
* Admin write-surface API tests (Session 18).
|
|
7
|
+
*
|
|
8
|
+
* Covers the auth gate + provider write methods on DashboardAPI: auth-status
|
|
9
|
+
* parsing, login/setup persisting the Bearer token, save/delete/test provider
|
|
10
|
+
* sending the token + parsing the (possibly masked) refreshed row, 401 error
|
|
11
|
+
* propagation (the session-expired signal the panel needs), and the same
|
|
12
|
+
* stale-server HTML-200 degradation every other dashboard fetch has.
|
|
13
|
+
*/
|
|
14
|
+
const vitest_1 = require("vitest");
|
|
15
|
+
const api_1 = require("./api");
|
|
16
|
+
const jsonResponse = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
17
|
+
status,
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
});
|
|
20
|
+
const htmlResponse = () => new Response('<!DOCTYPE html><html><body>SPA fallback</body></html>', {
|
|
21
|
+
status: 200,
|
|
22
|
+
headers: { 'Content-Type': 'text/html' },
|
|
23
|
+
});
|
|
24
|
+
const AUTHED = { configured: true, authenticated: true, user: 'admin', role: 'admin' };
|
|
25
|
+
const USERS = [
|
|
26
|
+
{ user: 'admin', role: 'admin', createdAt: 100 },
|
|
27
|
+
{ user: 'view', role: 'viewer', createdAt: 200 },
|
|
28
|
+
];
|
|
29
|
+
const ROW = { type: 'groq', configured: true, keySource: 'config', keyMasked: 'gsk_…abcd', model: 'm1', baseUrl: 'https://api.groq.com' };
|
|
30
|
+
(0, vitest_1.describe)('DashboardAPI admin auth surface', () => {
|
|
31
|
+
(0, vitest_1.afterEach)(() => {
|
|
32
|
+
vitest_1.vi.restoreAllMocks();
|
|
33
|
+
(0, api_1.setAdminToken)(null);
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)('parses /api/admin/auth-status (configured + authenticated)', async () => {
|
|
36
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse(AUTHED));
|
|
37
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
38
|
+
(0, vitest_1.expect)(await api.fetchAdminAuthStatus()).toEqual(AUTHED);
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)('returns null for a stale server answering HTML — no crash', async () => {
|
|
41
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(htmlResponse());
|
|
42
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
43
|
+
(0, vitest_1.expect)(await api.fetchAdminAuthStatus()).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)('login persists the token and clears it on logout', async () => {
|
|
46
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, user: 'admin', token: 'tok-1' }));
|
|
47
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
48
|
+
const r = await api.adminLogin('admin', 'secret-pass');
|
|
49
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
50
|
+
(0, vitest_1.expect)((0, api_1.getAdminToken)()).toBe('tok-1');
|
|
51
|
+
await api.adminLogout();
|
|
52
|
+
(0, vitest_1.expect)((0, api_1.getAdminToken)()).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
(0, vitest_1.it)('login surfaces the 401 error message (wrong password) without persisting a token', async () => {
|
|
55
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: false, error: 'Invalid username or password.' }, 401));
|
|
56
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
57
|
+
const r = await api.adminLogin('admin', 'wrong');
|
|
58
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
59
|
+
(0, vitest_1.expect)(r.unauthorized).toBe(true);
|
|
60
|
+
(0, vitest_1.expect)(r.error).toContain('Invalid');
|
|
61
|
+
(0, vitest_1.expect)((0, api_1.getAdminToken)()).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)('setup persists the token on success', async () => {
|
|
64
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, user: 'admin', token: 'tok-setup' }));
|
|
65
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
66
|
+
const r = await api.adminSetup('admin', 'long-enough-pass');
|
|
67
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
68
|
+
(0, vitest_1.expect)((0, api_1.getAdminToken)()).toBe('tok-setup');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.describe)('DashboardAPI provider write surface', () => {
|
|
72
|
+
(0, vitest_1.afterEach)(() => {
|
|
73
|
+
vitest_1.vi.restoreAllMocks();
|
|
74
|
+
(0, api_1.setAdminToken)(null);
|
|
75
|
+
});
|
|
76
|
+
const expectAuthHeader = (input, init) => {
|
|
77
|
+
(0, vitest_1.expect)(String(input)).toContain('/api/admin/providers/groq');
|
|
78
|
+
(0, vitest_1.expect)(init?.headers).toMatchObject({ Authorization: 'Bearer tok-auth' });
|
|
79
|
+
};
|
|
80
|
+
(0, vitest_1.it)('saveProvider sends the token and returns the refreshed masked row', async () => {
|
|
81
|
+
(0, api_1.setAdminToken)('tok-auth');
|
|
82
|
+
const spy = vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, provider: ROW }));
|
|
83
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
84
|
+
const r = await api.saveProvider('groq', { apiKey: 'gsk_new', baseUrl: 'https://api.groq.com' });
|
|
85
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
86
|
+
(0, vitest_1.expect)(r.provider?.keyMasked).toBe('gsk_…abcd');
|
|
87
|
+
(0, vitest_1.expect)(spy).toHaveBeenCalledTimes(1);
|
|
88
|
+
const [input, init] = spy.mock.calls[0];
|
|
89
|
+
expectAuthHeader(input, init);
|
|
90
|
+
(0, vitest_1.expect)(JSON.parse(init.body)).toMatchObject({ apiKey: 'gsk_new' });
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)('saveProvider without a token reports unauthorized (401) so the panel shows login', async () => {
|
|
93
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: false, error: 'Not authenticated — log in first.' }, 401));
|
|
94
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
95
|
+
const r = await api.saveProvider('groq', { apiKey: 'x' });
|
|
96
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
97
|
+
(0, vitest_1.expect)(r.unauthorized).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
(0, vitest_1.it)('deleteProvider returns ok + the cleared row', async () => {
|
|
100
|
+
(0, api_1.setAdminToken)('tok-auth');
|
|
101
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, cleared: true, provider: { ...ROW, configured: false, keySource: 'none', keyMasked: null } }));
|
|
102
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
103
|
+
const r = await api.deleteProvider('groq');
|
|
104
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
105
|
+
(0, vitest_1.expect)(r.cleared).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
(0, vitest_1.it)('testProvider parses the model list on success and the error message on failure', async () => {
|
|
108
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, models: ['llama-3.3-70b', 'mixtral'] }));
|
|
109
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
110
|
+
const ok = await api.testProvider('groq');
|
|
111
|
+
(0, vitest_1.expect)(ok.ok).toBe(true);
|
|
112
|
+
(0, vitest_1.expect)(ok.models).toHaveLength(2);
|
|
113
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: false, error: '401 invalid key' }));
|
|
114
|
+
const bad = await api.testProvider('nim');
|
|
115
|
+
(0, vitest_1.expect)(bad.ok).toBe(false);
|
|
116
|
+
(0, vitest_1.expect)(bad.error).toContain('401');
|
|
117
|
+
});
|
|
118
|
+
(0, vitest_1.it)('degrades to a network-failure message (never throws)', async () => {
|
|
119
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('Failed to fetch'));
|
|
120
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
121
|
+
const r = await api.saveProvider('groq', { apiKey: 'x' });
|
|
122
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
123
|
+
(0, vitest_1.expect)(r.error).toMatch(/reach/i);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
(0, vitest_1.describe)('DashboardAPI user management (role.manage)', () => {
|
|
127
|
+
(0, vitest_1.afterEach)(() => {
|
|
128
|
+
vitest_1.vi.restoreAllMocks();
|
|
129
|
+
(0, api_1.setAdminToken)(null);
|
|
130
|
+
});
|
|
131
|
+
(0, vitest_1.it)('fetchAdminUsers parses the user list with the token attached', async () => {
|
|
132
|
+
(0, api_1.setAdminToken)('tok-auth');
|
|
133
|
+
const spy = vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, users: USERS }));
|
|
134
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
135
|
+
const r = await api.fetchAdminUsers();
|
|
136
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
137
|
+
(0, vitest_1.expect)(r.users).toHaveLength(2);
|
|
138
|
+
(0, vitest_1.expect)(r.users[0]).toMatchObject({ user: 'admin', role: 'admin' });
|
|
139
|
+
const [input, init] = spy.mock.calls[0];
|
|
140
|
+
(0, vitest_1.expect)(String(input)).toContain('/api/admin/users');
|
|
141
|
+
(0, vitest_1.expect)(init?.headers).toMatchObject({ Authorization: 'Bearer tok-auth' });
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.it)('addAdminUser POSTs user/password/role and clears the form state on success', async () => {
|
|
144
|
+
(0, api_1.setAdminToken)('tok-auth');
|
|
145
|
+
const spy = vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true }));
|
|
146
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
147
|
+
const r = await api.addAdminUser('view', 'viewer-pass-12', 'viewer');
|
|
148
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
149
|
+
const [input, init] = spy.mock.calls[0];
|
|
150
|
+
(0, vitest_1.expect)(String(input)).toContain('/api/admin/users');
|
|
151
|
+
(0, vitest_1.expect)(JSON.parse(init.body)).toEqual({ user: 'view', password: 'viewer-pass-12', role: 'viewer' });
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('reports forbidden (403) when the role may not manage users', async () => {
|
|
154
|
+
(0, api_1.setAdminToken)('tok-viewer');
|
|
155
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: false, error: "Access denied — role 'viewer' cannot manage users (requires admin)." }, 403));
|
|
156
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
157
|
+
const r = await api.fetchAdminUsers();
|
|
158
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
159
|
+
(0, vitest_1.expect)(r.forbidden).toBe(true);
|
|
160
|
+
(0, vitest_1.expect)(r.error).toContain('Access denied');
|
|
161
|
+
});
|
|
162
|
+
(0, vitest_1.it)('removeAdminUser DELETEs by name', async () => {
|
|
163
|
+
(0, api_1.setAdminToken)('tok-auth');
|
|
164
|
+
const spy = vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, removed: true }));
|
|
165
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
166
|
+
const r = await api.removeAdminUser('view');
|
|
167
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
168
|
+
const [input, init] = spy.mock.calls[0];
|
|
169
|
+
(0, vitest_1.expect)(String(input)).toContain('/api/admin/users/view');
|
|
170
|
+
(0, vitest_1.expect)(init?.method).toBe('DELETE');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Admin command-runner fetch tests (Session 17).
|
|
4
|
+
*
|
|
5
|
+
* fetchAdminChecks() drives the dashboard's /api/admin/checks endpoint — the
|
|
6
|
+
* on-demand execution of ALL state commands (doctor system + enterprise
|
|
7
|
+
* checks + masked provider summary). Regression coverage for the same
|
|
8
|
+
* parseJsonOrNull contract every other dashboard fetch uses: a stale server
|
|
9
|
+
* answering HTML must degrade to null, never crash.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const vitest_1 = require("vitest");
|
|
13
|
+
const api_1 = require("./api");
|
|
14
|
+
const jsonResponse = (data) => new Response(JSON.stringify(data), {
|
|
15
|
+
status: 200,
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
});
|
|
18
|
+
const htmlResponse = () => new Response('<!DOCTYPE html><html><body>SPA fallback</body></html>', {
|
|
19
|
+
status: 200,
|
|
20
|
+
headers: { 'Content-Type': 'text/html' },
|
|
21
|
+
});
|
|
22
|
+
const VALID_PAYLOAD = {
|
|
23
|
+
system: [{ name: 'Config Directory', status: 'pass', message: '~/.nuvira/ exists' }],
|
|
24
|
+
enterprise: [{ name: 'Secrets Backend', status: 'warn', message: 'keys in config' }],
|
|
25
|
+
providers: [{ type: 'groq', configured: true, keySource: 'env', keyMasked: 'gsk_…abcd' }],
|
|
26
|
+
serverTime: 123,
|
|
27
|
+
};
|
|
28
|
+
(0, vitest_1.describe)('DashboardAPI.fetchAdminChecks', () => {
|
|
29
|
+
(0, vitest_1.afterEach)(() => {
|
|
30
|
+
vitest_1.vi.restoreAllMocks();
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)('parses a real /api/admin/checks response', async () => {
|
|
33
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse(VALID_PAYLOAD));
|
|
34
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
35
|
+
const data = await api.fetchAdminChecks();
|
|
36
|
+
(0, vitest_1.expect)(data).toMatchObject({ serverTime: 123 });
|
|
37
|
+
(0, vitest_1.expect)(data.system[0].status).toBe('pass');
|
|
38
|
+
(0, vitest_1.expect)(data.providers[0].keySource).toBe('env');
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)('returns null when the server answers HTML (stale server) — no crash', async () => {
|
|
41
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(htmlResponse());
|
|
42
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
43
|
+
const data = await api.fetchAdminChecks();
|
|
44
|
+
(0, vitest_1.expect)(data).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)('returns null when the payload lacks the checks arrays', async () => {
|
|
47
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ serverTime: 1 }));
|
|
48
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
49
|
+
(0, vitest_1.expect)(await api.fetchAdminChecks()).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)('returns null when the payload lacks the providers array (panel would crash on .length)', async () => {
|
|
52
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({
|
|
53
|
+
system: [],
|
|
54
|
+
enterprise: [],
|
|
55
|
+
serverTime: 1,
|
|
56
|
+
}));
|
|
57
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
58
|
+
(0, vitest_1.expect)(await api.fetchAdminChecks()).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)('returns null on network failure', async () => {
|
|
61
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('down'));
|
|
62
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
63
|
+
(0, vitest_1.expect)(await api.fetchAdminChecks()).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Hub API client tests (I4 + I5).
|
|
4
|
+
*
|
|
5
|
+
* fetchHub() — the aggregated read the 4-tab panel polls. setToolsetEnabled()
|
|
6
|
+
* — the admin-gated write (PUT /api/admin/hub/toolsets/<name>) that persists
|
|
7
|
+
* the toggle to buffconfig. Both follow the same stale-server contract as the
|
|
8
|
+
* other dashboard fetches: HTML-200 / malformed / network failure degrade to
|
|
9
|
+
* null or { ok:false }, never a crash.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const vitest_1 = require("vitest");
|
|
13
|
+
const api_1 = require("./api");
|
|
14
|
+
const jsonResponse = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
15
|
+
status,
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
});
|
|
18
|
+
const htmlResponse = () => new Response('<!DOCTYPE html><html><body>SPA fallback</body></html>', {
|
|
19
|
+
status: 200,
|
|
20
|
+
headers: { 'Content-Type': 'text/html' },
|
|
21
|
+
});
|
|
22
|
+
const HUB_PAYLOAD = {
|
|
23
|
+
toolsets: {
|
|
24
|
+
toolsets: [
|
|
25
|
+
{ name: 'core', label: 'Core', description: 'Pipeline actions', enabled: true, tools: ['build'], toolCount: 1 },
|
|
26
|
+
{ name: 'web', label: 'Web research', description: 'Search + read', enabled: true, tools: ['web_search'], toolCount: 1 },
|
|
27
|
+
],
|
|
28
|
+
enabled: 2,
|
|
29
|
+
disabled: 0,
|
|
30
|
+
totalTools: 2,
|
|
31
|
+
},
|
|
32
|
+
channels: {
|
|
33
|
+
delivery: { total: 1, pending: 1, sent: 0, failed: 0, recent: [] },
|
|
34
|
+
aliases: [],
|
|
35
|
+
reachable: [],
|
|
36
|
+
platforms: [
|
|
37
|
+
{ platform: 'email', label: 'Email (SMTP)', configured: false, envVars: ['BUFF_SMTP_HOST', 'BUFF_SMTP_USER'] },
|
|
38
|
+
{ platform: 'signal', label: 'Signal (signal-cli-rest-api)', configured: true, envVars: ['BUFF_SIGNAL_ACCOUNT'] },
|
|
39
|
+
],
|
|
40
|
+
policies: {},
|
|
41
|
+
contacts: [],
|
|
42
|
+
statusRecipients: [],
|
|
43
|
+
statusRecipientDisplay: {},
|
|
44
|
+
inbox: { total: 0, pipeline: 0, chat: 0, help: 0, refused: 0, recent: [] },
|
|
45
|
+
},
|
|
46
|
+
artifacts: { totalSessions: 0, totalArtifacts: 0, sessions: [] },
|
|
47
|
+
skills: { compiled: [], hub: [], total: 0 },
|
|
48
|
+
adminConfigured: false,
|
|
49
|
+
serverTime: 123,
|
|
50
|
+
};
|
|
51
|
+
(0, vitest_1.describe)('DashboardAPI.fetchHub', () => {
|
|
52
|
+
(0, vitest_1.afterEach)(() => {
|
|
53
|
+
vitest_1.vi.restoreAllMocks();
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.it)('parses a real /api/hub response', async () => {
|
|
56
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse(HUB_PAYLOAD));
|
|
57
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
58
|
+
const data = await api.fetchHub();
|
|
59
|
+
(0, vitest_1.expect)(data).not.toBeNull();
|
|
60
|
+
(0, vitest_1.expect)(data.toolsets.toolsets).toHaveLength(2);
|
|
61
|
+
(0, vitest_1.expect)(data.adminConfigured).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)('returns null when the payload lacks the toolsets array (stale server shape)', async () => {
|
|
64
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ serverTime: 1 }));
|
|
65
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
66
|
+
(0, vitest_1.expect)(await api.fetchHub()).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.it)('returns null on an HTML answer (stale server) — no crash', async () => {
|
|
69
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(htmlResponse());
|
|
70
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
71
|
+
(0, vitest_1.expect)(await api.fetchHub()).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)('returns null on network failure', async () => {
|
|
74
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('down'));
|
|
75
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
76
|
+
(0, vitest_1.expect)(await api.fetchHub()).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
(0, vitest_1.describe)('DashboardAPI.setToolsetEnabled', () => {
|
|
80
|
+
(0, vitest_1.afterEach)(() => {
|
|
81
|
+
vitest_1.vi.restoreAllMocks();
|
|
82
|
+
(0, api_1.setAdminToken)(null);
|
|
83
|
+
});
|
|
84
|
+
(0, vitest_1.it)('PUTs { enabled } to the admin route with the bearer token', async () => {
|
|
85
|
+
(0, api_1.setAdminToken)('tok-123');
|
|
86
|
+
const fetchMock = vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true, toolset: 'web', enabled: false }));
|
|
87
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
88
|
+
const r = await api.setToolsetEnabled('web', false);
|
|
89
|
+
(0, vitest_1.expect)(r.ok).toBe(true);
|
|
90
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
91
|
+
(0, vitest_1.expect)(String(url)).toBe('http://test/api/admin/hub/toolsets/web');
|
|
92
|
+
(0, vitest_1.expect)(init?.method).toBe('PUT');
|
|
93
|
+
(0, vitest_1.expect)(JSON.parse(String(init?.body))).toEqual({ enabled: false });
|
|
94
|
+
(0, vitest_1.expect)((init?.headers).Authorization).toBe('Bearer tok-123');
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)('surfaces 401 as unauthorized (session expired)', async () => {
|
|
97
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: false, error: 'Not authenticated' }, 401));
|
|
98
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
99
|
+
const r = await api.setToolsetEnabled('web', true);
|
|
100
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
101
|
+
(0, vitest_1.expect)(r.unauthorized).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.it)('surfaces 403 as forbidden (role cannot change capabilities)', async () => {
|
|
104
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: false, error: 'Access denied' }, 403));
|
|
105
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
106
|
+
const r = await api.setToolsetEnabled('web', true);
|
|
107
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
108
|
+
(0, vitest_1.expect)(r.forbidden).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.it)('returns a friendly error on network failure', async () => {
|
|
111
|
+
vitest_1.vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('down'));
|
|
112
|
+
const api = new api_1.DashboardAPI('http://test');
|
|
113
|
+
const r = await api.setToolsetEnabled('web', true);
|
|
114
|
+
(0, vitest_1.expect)(r.ok).toBe(false);
|
|
115
|
+
(0, vitest_1.expect)(r.error).toContain('Could not reach');
|
|
116
|
+
});
|
|
117
|
+
});
|