feat(status): show SecretBackend health in mcpctl status
Some checks failed
CI/CD / lint (pull_request) Successful in 1m3s
CI/CD / test (pull_request) Successful in 1m17s
CI/CD / typecheck (pull_request) Successful in 2m33s
CI/CD / smoke (pull_request) Failing after 1m52s
CI/CD / build (pull_request) Successful in 4m55s
CI/CD / publish (pull_request) Has been skipped
Some checks failed
CI/CD / lint (pull_request) Successful in 1m3s
CI/CD / test (pull_request) Successful in 1m17s
CI/CD / typecheck (pull_request) Successful in 2m33s
CI/CD / smoke (pull_request) Failing after 1m52s
CI/CD / build (pull_request) Successful in 4m55s
CI/CD / publish (pull_request) Has been skipped
Adds a 'Secrets:' line to mcpctl status (and a secretBackends array to JSON output) showing each backend healthy (✓) or, when tokenMeta.lastRotationError is set (e.g. a dead OpenBao token), red ✗ with the error inline. Makes a recurrence of BACKEND_TOKEN_DEAD visible at a glance instead of only in mcpd logs. Verified live: 'Secrets: bao* ✓, default ✓'. +3 tests (28 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ function baseDeps(overrides?: Partial<StatusCommandDeps>): Partial<StatusCommand
|
||||
fetchProviders: async () => null,
|
||||
fetchServerLlms: async () => null,
|
||||
probeServerLlm: async () => ({ ok: true, ms: 12, say: 'hi' }),
|
||||
fetchSecretBackends: async () => null,
|
||||
isTTY: false,
|
||||
...overrides,
|
||||
};
|
||||
@@ -45,6 +46,39 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('status command', () => {
|
||||
it('shows a healthy secret backend in the Secrets line', async () => {
|
||||
const cmd = createStatusCommand(baseDeps({
|
||||
fetchSecretBackends: async () => [
|
||||
{ name: 'bao', type: 'openbao', isDefault: true, tokenMeta: { lastRotationError: null } },
|
||||
{ name: 'default', type: 'plaintext' },
|
||||
],
|
||||
}));
|
||||
await cmd.parseAsync([], { from: 'user' });
|
||||
const out = output.join('\n');
|
||||
expect(out).toContain('Secrets:');
|
||||
expect(out).toContain('bao* ✓');
|
||||
expect(out).toContain('default ✓');
|
||||
});
|
||||
|
||||
it('flags a dead secret-backend token in the Secrets line', async () => {
|
||||
const cmd = createStatusCommand(baseDeps({
|
||||
fetchSecretBackends: async () => [
|
||||
{ name: 'bao', type: 'openbao', isDefault: true, tokenMeta: { lastRotationError: 'BACKEND_TOKEN_DEAD: rejected the stored token\nmore detail' } },
|
||||
],
|
||||
}));
|
||||
await cmd.parseAsync([], { from: 'user' });
|
||||
const out = output.join('\n');
|
||||
expect(out).toContain('bao* ✗');
|
||||
expect(out).toContain('BACKEND_TOKEN_DEAD');
|
||||
expect(out).not.toContain('more detail'); // only first line, truncated
|
||||
});
|
||||
|
||||
it('omits the Secrets line when mcpd returns no backends', async () => {
|
||||
const cmd = createStatusCommand(baseDeps({ fetchSecretBackends: async () => null }));
|
||||
await cmd.parseAsync([], { from: 'user' });
|
||||
expect(output.join('\n')).not.toContain('Secrets:');
|
||||
});
|
||||
|
||||
it('shows status in table format', async () => {
|
||||
const cmd = createStatusCommand(baseDeps());
|
||||
await cmd.parseAsync([], { from: 'user' });
|
||||
|
||||
Reference in New Issue
Block a user