2026-02-21 05:40:46 +00:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
import Fastify from 'fastify';
|
|
|
|
|
import { BackupService } from '../src/services/backup/backup-service.js';
|
|
|
|
|
import { RestoreService } from '../src/services/backup/restore-service.js';
|
|
|
|
|
import { encrypt, decrypt, isSensitiveKey } from '../src/services/backup/crypto.js';
|
|
|
|
|
import { registerBackupRoutes } from '../src/routes/backup.js';
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
import type { IMcpServerRepository, ISecretRepository } from '../src/repositories/interfaces.js';
|
2026-02-21 05:40:46 +00:00
|
|
|
import type { IProjectRepository } from '../src/repositories/project.repository.js';
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
import type { IUserRepository } from '../src/repositories/user.repository.js';
|
|
|
|
|
import type { IGroupRepository } from '../src/repositories/group.repository.js';
|
|
|
|
|
import type { IRbacDefinitionRepository } from '../src/repositories/rbac-definition.repository.js';
|
2026-02-21 05:40:46 +00:00
|
|
|
|
|
|
|
|
// Mock data
|
|
|
|
|
const mockServers = [
|
|
|
|
|
{
|
|
|
|
|
id: 's1', name: 'github', description: 'GitHub MCP', packageName: '@mcp/github',
|
|
|
|
|
dockerImage: null, transport: 'STDIO' as const, repositoryUrl: null,
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
env: [], version: 1, createdAt: new Date(), updatedAt: new Date(),
|
2026-02-21 05:40:46 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 's2', name: 'slack', description: 'Slack MCP', packageName: null,
|
|
|
|
|
dockerImage: 'mcp/slack:latest', transport: 'SSE' as const, repositoryUrl: null,
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
env: [], version: 1, createdAt: new Date(), updatedAt: new Date(),
|
2026-02-21 05:40:46 +00:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const mockSecrets = [
|
2026-02-21 05:40:46 +00:00
|
|
|
{
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
id: 'sec1', name: 'github-secrets',
|
|
|
|
|
data: { GITHUB_TOKEN: 'ghp_secret123' },
|
2026-02-21 05:40:46 +00:00
|
|
|
version: 1, createdAt: new Date(), updatedAt: new Date(),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const mockProjects = [
|
|
|
|
|
{
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
id: 'proj1', name: 'my-project', description: 'Test project', proxyMode: 'direct', llmProvider: null, llmModel: null,
|
2026-02-21 05:40:46 +00:00
|
|
|
ownerId: 'user1', version: 1, createdAt: new Date(), updatedAt: new Date(),
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
servers: [{ id: 'ps1', server: { id: 's1', name: 'github' } }],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const mockUsers = [
|
|
|
|
|
{ id: 'u1', email: 'alice@test.com', name: 'Alice', role: 'ADMIN', provider: null, externalId: null, version: 1, createdAt: new Date(), updatedAt: new Date() },
|
|
|
|
|
{ id: 'u2', email: 'bob@test.com', name: null, role: 'USER', provider: 'oidc', externalId: null, version: 1, createdAt: new Date(), updatedAt: new Date() },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const mockGroups = [
|
|
|
|
|
{
|
|
|
|
|
id: 'g1', name: 'dev-team', description: 'Developers', version: 1, createdAt: new Date(), updatedAt: new Date(),
|
|
|
|
|
members: [
|
|
|
|
|
{ id: 'gm1', user: { id: 'u1', email: 'alice@test.com', name: 'Alice' } },
|
|
|
|
|
{ id: 'gm2', user: { id: 'u2', email: 'bob@test.com', name: null } },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const mockRbacDefinitions = [
|
|
|
|
|
{
|
|
|
|
|
id: 'rbac1', name: 'admins', version: 1, createdAt: new Date(), updatedAt: new Date(),
|
|
|
|
|
subjects: [{ kind: 'User', name: 'alice@test.com' }],
|
|
|
|
|
roleBindings: [{ role: 'edit', resource: '*' }],
|
2026-02-21 05:40:46 +00:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function mockServerRepo(): IMcpServerRepository {
|
|
|
|
|
return {
|
|
|
|
|
findAll: vi.fn(async () => [...mockServers]),
|
|
|
|
|
findById: vi.fn(async (id: string) => mockServers.find((s) => s.id === id) ?? null),
|
|
|
|
|
findByName: vi.fn(async (name: string) => mockServers.find((s) => s.name === name) ?? null),
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
create: vi.fn(async (data) => ({ id: 'new-s', ...data, env: [], version: 1, createdAt: new Date(), updatedAt: new Date() } as typeof mockServers[0])),
|
2026-02-21 05:40:46 +00:00
|
|
|
update: vi.fn(async (id, data) => ({ ...mockServers.find((s) => s.id === id)!, ...data })),
|
|
|
|
|
delete: vi.fn(async () => {}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
function mockSecretRepo(): ISecretRepository {
|
2026-02-21 05:40:46 +00:00
|
|
|
return {
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
findAll: vi.fn(async () => [...mockSecrets]),
|
|
|
|
|
findById: vi.fn(async (id: string) => mockSecrets.find((s) => s.id === id) ?? null),
|
|
|
|
|
findByName: vi.fn(async (name: string) => mockSecrets.find((s) => s.name === name) ?? null),
|
|
|
|
|
create: vi.fn(async (data) => ({ id: 'new-sec', ...data, version: 1, createdAt: new Date(), updatedAt: new Date() } as typeof mockSecrets[0])),
|
|
|
|
|
update: vi.fn(async (id, data) => ({ ...mockSecrets.find((s) => s.id === id)!, ...data })),
|
2026-02-21 05:40:46 +00:00
|
|
|
delete: vi.fn(async () => {}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockProjectRepo(): IProjectRepository {
|
|
|
|
|
return {
|
|
|
|
|
findAll: vi.fn(async () => [...mockProjects]),
|
|
|
|
|
findById: vi.fn(async (id: string) => mockProjects.find((p) => p.id === id) ?? null),
|
|
|
|
|
findByName: vi.fn(async () => null),
|
2026-02-23 17:50:01 +00:00
|
|
|
create: vi.fn(async (data) => ({ id: 'new-proj', ...data, servers: [], version: 1, createdAt: new Date(), updatedAt: new Date() } as typeof mockProjects[0])),
|
2026-02-21 05:40:46 +00:00
|
|
|
update: vi.fn(async (id, data) => ({ ...mockProjects.find((p) => p.id === id)!, ...data })),
|
|
|
|
|
delete: vi.fn(async () => {}),
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
setServers: vi.fn(async () => {}),
|
2026-02-23 17:50:01 +00:00
|
|
|
addServer: vi.fn(async () => {}),
|
|
|
|
|
removeServer: vi.fn(async () => {}),
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockUserRepo(): IUserRepository {
|
|
|
|
|
return {
|
|
|
|
|
findAll: vi.fn(async () => [...mockUsers]),
|
|
|
|
|
findById: vi.fn(async (id: string) => mockUsers.find((u) => u.id === id) ?? null),
|
|
|
|
|
findByEmail: vi.fn(async (email: string) => mockUsers.find((u) => u.email === email) ?? null),
|
|
|
|
|
create: vi.fn(async (data) => ({ id: 'new-u', ...data, provider: null, externalId: null, version: 1, createdAt: new Date(), updatedAt: new Date() } as typeof mockUsers[0])),
|
|
|
|
|
delete: vi.fn(async () => {}),
|
|
|
|
|
count: vi.fn(async () => mockUsers.length),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockGroupRepo(): IGroupRepository {
|
|
|
|
|
return {
|
|
|
|
|
findAll: vi.fn(async () => [...mockGroups]),
|
|
|
|
|
findById: vi.fn(async (id: string) => mockGroups.find((g) => g.id === id) ?? null),
|
|
|
|
|
findByName: vi.fn(async (name: string) => mockGroups.find((g) => g.name === name) ?? null),
|
|
|
|
|
create: vi.fn(async (data) => ({ id: 'new-g', ...data, version: 1, createdAt: new Date(), updatedAt: new Date() } as typeof mockGroups[0])),
|
|
|
|
|
update: vi.fn(async (id, data) => ({ ...mockGroups.find((g) => g.id === id)!, ...data })),
|
|
|
|
|
delete: vi.fn(async () => {}),
|
|
|
|
|
setMembers: vi.fn(async () => {}),
|
|
|
|
|
findGroupsForUser: vi.fn(async () => []),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockRbacRepo(): IRbacDefinitionRepository {
|
|
|
|
|
return {
|
|
|
|
|
findAll: vi.fn(async () => [...mockRbacDefinitions]),
|
|
|
|
|
findById: vi.fn(async (id: string) => mockRbacDefinitions.find((r) => r.id === id) ?? null),
|
|
|
|
|
findByName: vi.fn(async (name: string) => mockRbacDefinitions.find((r) => r.name === name) ?? null),
|
|
|
|
|
create: vi.fn(async (data) => ({ id: 'new-rbac', ...data, version: 1, createdAt: new Date(), updatedAt: new Date() } as typeof mockRbacDefinitions[0])),
|
|
|
|
|
update: vi.fn(async (id, data) => ({ ...mockRbacDefinitions.find((r) => r.id === id)!, ...data })),
|
|
|
|
|
delete: vi.fn(async () => {}),
|
2026-02-21 05:40:46 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('Crypto', () => {
|
|
|
|
|
it('encrypts and decrypts successfully', () => {
|
|
|
|
|
const data = 'hello secret world';
|
|
|
|
|
const password = 'my-password-123';
|
|
|
|
|
const encrypted = encrypt(data, password);
|
|
|
|
|
|
|
|
|
|
expect(encrypted.algorithm).toBe('aes-256-gcm');
|
|
|
|
|
expect(encrypted.ciphertext).not.toBe(data);
|
|
|
|
|
|
|
|
|
|
const decrypted = decrypt(encrypted, password);
|
|
|
|
|
expect(decrypted).toBe(data);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('fails with wrong password', () => {
|
|
|
|
|
const encrypted = encrypt('secret', 'correct-password');
|
|
|
|
|
expect(() => decrypt(encrypted, 'wrong-password')).toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles large data', () => {
|
|
|
|
|
const data = 'x'.repeat(10000);
|
|
|
|
|
const encrypted = encrypt(data, 'pass');
|
|
|
|
|
expect(decrypt(encrypted, 'pass')).toBe(data);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('detects sensitive keys', () => {
|
|
|
|
|
expect(isSensitiveKey('GITHUB_TOKEN')).toBe(true);
|
|
|
|
|
expect(isSensitiveKey('API_KEY')).toBe(true);
|
|
|
|
|
expect(isSensitiveKey('DATABASE_PASSWORD')).toBe(true);
|
|
|
|
|
expect(isSensitiveKey('AWS_SECRET')).toBe(true);
|
|
|
|
|
expect(isSensitiveKey('MY_SECRET_KEY')).toBe(true);
|
|
|
|
|
expect(isSensitiveKey('CREDENTIALS')).toBe(true);
|
|
|
|
|
expect(isSensitiveKey('PORT')).toBe(false);
|
|
|
|
|
expect(isSensitiveKey('DATABASE_URL')).toBe(false);
|
|
|
|
|
expect(isSensitiveKey('NODE_ENV')).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('BackupService', () => {
|
|
|
|
|
let backupService: BackupService;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
backupService = new BackupService(mockServerRepo(), mockProjectRepo(), mockSecretRepo(), mockUserRepo(), mockGroupRepo(), mockRbacRepo());
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates backup with all resources', async () => {
|
|
|
|
|
const bundle = await backupService.createBackup();
|
|
|
|
|
|
|
|
|
|
expect(bundle.version).toBe('1');
|
|
|
|
|
expect(bundle.encrypted).toBe(false);
|
|
|
|
|
expect(bundle.servers).toHaveLength(2);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(bundle.secrets).toHaveLength(1);
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(bundle.projects).toHaveLength(1);
|
|
|
|
|
expect(bundle.servers[0]!.name).toBe('github');
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(bundle.secrets[0]!.name).toBe('github-secrets');
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(bundle.projects[0]!.name).toBe('my-project');
|
|
|
|
|
});
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
it('includes users in backup', async () => {
|
|
|
|
|
const bundle = await backupService.createBackup();
|
|
|
|
|
expect(bundle.users).toHaveLength(2);
|
|
|
|
|
expect(bundle.users![0]!.email).toBe('alice@test.com');
|
|
|
|
|
expect(bundle.users![0]!.role).toBe('ADMIN');
|
|
|
|
|
expect(bundle.users![1]!.email).toBe('bob@test.com');
|
|
|
|
|
expect(bundle.users![1]!.provider).toBe('oidc');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('includes groups in backup with member emails', async () => {
|
|
|
|
|
const bundle = await backupService.createBackup();
|
|
|
|
|
expect(bundle.groups).toHaveLength(1);
|
|
|
|
|
expect(bundle.groups![0]!.name).toBe('dev-team');
|
|
|
|
|
expect(bundle.groups![0]!.memberEmails).toEqual(['alice@test.com', 'bob@test.com']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('includes rbac bindings in backup', async () => {
|
|
|
|
|
const bundle = await backupService.createBackup();
|
|
|
|
|
expect(bundle.rbacBindings).toHaveLength(1);
|
|
|
|
|
expect(bundle.rbacBindings![0]!.name).toBe('admins');
|
|
|
|
|
expect(bundle.rbacBindings![0]!.subjects).toEqual([{ kind: 'User', name: 'alice@test.com' }]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-23 17:50:01 +00:00
|
|
|
it('includes enriched projects with server names', async () => {
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
const bundle = await backupService.createBackup();
|
|
|
|
|
const proj = bundle.projects[0]!;
|
|
|
|
|
expect(proj.proxyMode).toBe('direct');
|
|
|
|
|
expect(proj.serverNames).toEqual(['github']);
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 05:40:46 +00:00
|
|
|
it('filters resources', async () => {
|
|
|
|
|
const bundle = await backupService.createBackup({ resources: ['servers'] });
|
|
|
|
|
expect(bundle.servers).toHaveLength(2);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(bundle.secrets).toHaveLength(0);
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(bundle.projects).toHaveLength(0);
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
expect(bundle.users).toHaveLength(0);
|
|
|
|
|
expect(bundle.groups).toHaveLength(0);
|
|
|
|
|
expect(bundle.rbacBindings).toHaveLength(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('filters to only users', async () => {
|
|
|
|
|
const bundle = await backupService.createBackup({ resources: ['users'] });
|
|
|
|
|
expect(bundle.servers).toHaveLength(0);
|
|
|
|
|
expect(bundle.users).toHaveLength(2);
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
it('encrypts sensitive secret values when password provided', async () => {
|
2026-02-21 05:40:46 +00:00
|
|
|
const bundle = await backupService.createBackup({ password: 'test-pass' });
|
|
|
|
|
|
|
|
|
|
expect(bundle.encrypted).toBe(true);
|
|
|
|
|
expect(bundle.encryptedSecrets).toBeDefined();
|
|
|
|
|
// The GITHUB_TOKEN should be replaced with placeholder
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const data = bundle.secrets[0]!.data;
|
|
|
|
|
expect(data['GITHUB_TOKEN']).toContain('__ENCRYPTED:');
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles empty repositories', async () => {
|
|
|
|
|
const emptyServerRepo = mockServerRepo();
|
|
|
|
|
(emptyServerRepo.findAll as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const emptySecretRepo = mockSecretRepo();
|
|
|
|
|
(emptySecretRepo.findAll as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
2026-02-21 05:40:46 +00:00
|
|
|
const emptyProjectRepo = mockProjectRepo();
|
|
|
|
|
(emptyProjectRepo.findAll as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
const emptyUserRepo = mockUserRepo();
|
|
|
|
|
(emptyUserRepo.findAll as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
|
|
|
const emptyGroupRepo = mockGroupRepo();
|
|
|
|
|
(emptyGroupRepo.findAll as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
|
|
|
const emptyRbacRepo = mockRbacRepo();
|
|
|
|
|
(emptyRbacRepo.findAll as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const service = new BackupService(emptyServerRepo, emptyProjectRepo, emptySecretRepo, emptyUserRepo, emptyGroupRepo, emptyRbacRepo);
|
2026-02-21 05:40:46 +00:00
|
|
|
const bundle = await service.createBackup();
|
|
|
|
|
|
|
|
|
|
expect(bundle.servers).toHaveLength(0);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(bundle.secrets).toHaveLength(0);
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(bundle.projects).toHaveLength(0);
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
expect(bundle.users).toHaveLength(0);
|
|
|
|
|
expect(bundle.groups).toHaveLength(0);
|
|
|
|
|
expect(bundle.rbacBindings).toHaveLength(0);
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('RestoreService', () => {
|
|
|
|
|
let restoreService: RestoreService;
|
|
|
|
|
let serverRepo: IMcpServerRepository;
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
let secretRepo: ISecretRepository;
|
2026-02-21 05:40:46 +00:00
|
|
|
let projectRepo: IProjectRepository;
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
let userRepo: IUserRepository;
|
|
|
|
|
let groupRepo: IGroupRepository;
|
|
|
|
|
let rbacRepo: IRbacDefinitionRepository;
|
2026-02-21 05:40:46 +00:00
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
serverRepo = mockServerRepo();
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
secretRepo = mockSecretRepo();
|
2026-02-21 05:40:46 +00:00
|
|
|
projectRepo = mockProjectRepo();
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
userRepo = mockUserRepo();
|
|
|
|
|
groupRepo = mockGroupRepo();
|
|
|
|
|
rbacRepo = mockRbacRepo();
|
2026-02-21 05:40:46 +00:00
|
|
|
// Default: nothing exists yet
|
|
|
|
|
(serverRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
(secretRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
2026-02-21 05:40:46 +00:00
|
|
|
(projectRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
(userRepo.findByEmail as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
(groupRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
(rbacRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
restoreService = new RestoreService(serverRepo, projectRepo, secretRepo, userRepo, groupRepo, rbacRepo);
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const validBundle = {
|
|
|
|
|
version: '1',
|
|
|
|
|
mcpctlVersion: '0.1.0',
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
encrypted: false,
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
servers: [{ name: 'github', description: 'GitHub', packageName: null, dockerImage: null, transport: 'STDIO', repositoryUrl: null, env: [] }],
|
|
|
|
|
secrets: [{ name: 'github-secrets', data: { GITHUB_TOKEN: 'ghp_123' } }],
|
|
|
|
|
projects: [{ name: 'test-proj', description: 'Test' }],
|
2026-02-21 05:40:46 +00:00
|
|
|
};
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
const fullBundle = {
|
|
|
|
|
...validBundle,
|
|
|
|
|
users: [
|
|
|
|
|
{ email: 'alice@test.com', name: 'Alice', role: 'ADMIN', provider: null },
|
|
|
|
|
{ email: 'bob@test.com', name: null, role: 'USER', provider: 'oidc' },
|
|
|
|
|
],
|
|
|
|
|
groups: [
|
|
|
|
|
{ name: 'dev-team', description: 'Developers', memberEmails: ['alice@test.com', 'bob@test.com'] },
|
|
|
|
|
],
|
|
|
|
|
rbacBindings: [
|
|
|
|
|
{ name: 'admins', subjects: [{ kind: 'User', name: 'alice@test.com' }], roleBindings: [{ role: 'edit', resource: '*' }] },
|
|
|
|
|
],
|
|
|
|
|
projects: [
|
|
|
|
|
{ name: 'test-proj', description: 'Test', proxyMode: 'filtered', llmProvider: 'openai', llmModel: 'gpt-4', serverNames: ['github'], members: ['alice@test.com'] },
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-21 05:40:46 +00:00
|
|
|
it('validates valid bundle', () => {
|
|
|
|
|
expect(restoreService.validateBundle(validBundle)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects invalid bundle', () => {
|
|
|
|
|
expect(restoreService.validateBundle(null)).toBe(false);
|
|
|
|
|
expect(restoreService.validateBundle({})).toBe(false);
|
|
|
|
|
expect(restoreService.validateBundle({ version: '1' })).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
it('validates old bundles without new fields (backwards compatibility)', () => {
|
|
|
|
|
expect(restoreService.validateBundle(validBundle)).toBe(true);
|
|
|
|
|
// Old bundle has no users/groups/rbacBindings — should still validate
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 05:40:46 +00:00
|
|
|
it('restores all resources', async () => {
|
|
|
|
|
const result = await restoreService.restore(validBundle);
|
|
|
|
|
|
|
|
|
|
expect(result.serversCreated).toBe(1);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(result.secretsCreated).toBe(1);
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(result.projectsCreated).toBe(1);
|
|
|
|
|
expect(result.errors).toHaveLength(0);
|
|
|
|
|
expect(serverRepo.create).toHaveBeenCalled();
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(secretRepo.create).toHaveBeenCalled();
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(projectRepo.create).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
it('restores users', async () => {
|
|
|
|
|
const result = await restoreService.restore(fullBundle);
|
|
|
|
|
|
|
|
|
|
expect(result.usersCreated).toBe(2);
|
|
|
|
|
expect(userRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
|
|
|
email: 'alice@test.com',
|
|
|
|
|
name: 'Alice',
|
|
|
|
|
role: 'ADMIN',
|
|
|
|
|
passwordHash: '__RESTORED_MUST_RESET__',
|
|
|
|
|
}));
|
|
|
|
|
expect(userRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
|
|
|
email: 'bob@test.com',
|
|
|
|
|
role: 'USER',
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('restores groups with member resolution', async () => {
|
|
|
|
|
// After users are created, simulate they can be found by email
|
|
|
|
|
let callCount = 0;
|
|
|
|
|
(userRepo.findByEmail as ReturnType<typeof vi.fn>).mockImplementation(async (email: string) => {
|
|
|
|
|
// First calls during user restore return null (user doesn't exist yet)
|
|
|
|
|
// Later calls during group member resolution return the created user
|
|
|
|
|
callCount++;
|
|
|
|
|
if (callCount > 2) {
|
|
|
|
|
// After user creation phase, simulate finding created users
|
|
|
|
|
if (email === 'alice@test.com') return { id: 'new-u-alice', email };
|
|
|
|
|
if (email === 'bob@test.com') return { id: 'new-u-bob', email };
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await restoreService.restore(fullBundle);
|
|
|
|
|
|
|
|
|
|
expect(result.groupsCreated).toBe(1);
|
|
|
|
|
expect(groupRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
|
|
|
name: 'dev-team',
|
|
|
|
|
description: 'Developers',
|
|
|
|
|
}));
|
|
|
|
|
expect(groupRepo.setMembers).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('restores rbac bindings', async () => {
|
|
|
|
|
const result = await restoreService.restore(fullBundle);
|
|
|
|
|
|
|
|
|
|
expect(result.rbacCreated).toBe(1);
|
|
|
|
|
expect(rbacRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
|
|
|
name: 'admins',
|
|
|
|
|
subjects: [{ kind: 'User', name: 'alice@test.com' }],
|
|
|
|
|
roleBindings: [{ role: 'edit', resource: '*' }],
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-23 17:50:01 +00:00
|
|
|
it('restores enriched projects with server linking', async () => {
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
// Simulate servers exist (restored in prior step)
|
|
|
|
|
(serverRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
// After server restore, we can find them
|
|
|
|
|
let serverCallCount = 0;
|
|
|
|
|
(serverRepo.findByName as ReturnType<typeof vi.fn>).mockImplementation(async (name: string) => {
|
|
|
|
|
serverCallCount++;
|
|
|
|
|
// During server restore phase, first call returns null (server doesn't exist)
|
|
|
|
|
// During project restore phase, server should be found
|
|
|
|
|
if (serverCallCount > 1 && name === 'github') return { id: 'restored-s1', name: 'github' };
|
|
|
|
|
return null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await restoreService.restore(fullBundle);
|
|
|
|
|
|
|
|
|
|
expect(result.projectsCreated).toBe(1);
|
|
|
|
|
expect(projectRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
|
|
|
name: 'test-proj',
|
|
|
|
|
proxyMode: 'filtered',
|
|
|
|
|
llmProvider: 'openai',
|
|
|
|
|
llmModel: 'gpt-4',
|
|
|
|
|
}));
|
|
|
|
|
expect(projectRepo.setServers).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('restores old bundle without users/groups/rbac', async () => {
|
|
|
|
|
const result = await restoreService.restore(validBundle);
|
|
|
|
|
|
|
|
|
|
expect(result.serversCreated).toBe(1);
|
|
|
|
|
expect(result.secretsCreated).toBe(1);
|
|
|
|
|
expect(result.projectsCreated).toBe(1);
|
|
|
|
|
expect(result.usersCreated).toBe(0);
|
|
|
|
|
expect(result.groupsCreated).toBe(0);
|
|
|
|
|
expect(result.rbacCreated).toBe(0);
|
|
|
|
|
expect(result.errors).toHaveLength(0);
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 05:40:46 +00:00
|
|
|
it('skips existing resources with skip strategy', async () => {
|
|
|
|
|
(serverRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(mockServers[0]);
|
|
|
|
|
const result = await restoreService.restore(validBundle, { conflictStrategy: 'skip' });
|
|
|
|
|
|
|
|
|
|
expect(result.serversSkipped).toBe(1);
|
|
|
|
|
expect(result.serversCreated).toBe(0);
|
|
|
|
|
expect(serverRepo.create).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
it('skips existing users', async () => {
|
|
|
|
|
(userRepo.findByEmail as ReturnType<typeof vi.fn>).mockResolvedValue(mockUsers[0]);
|
|
|
|
|
const bundle = { ...validBundle, users: [{ email: 'alice@test.com', name: 'Alice', role: 'ADMIN', provider: null }] };
|
|
|
|
|
const result = await restoreService.restore(bundle, { conflictStrategy: 'skip' });
|
|
|
|
|
|
|
|
|
|
expect(result.usersSkipped).toBe(1);
|
|
|
|
|
expect(result.usersCreated).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('skips existing groups', async () => {
|
|
|
|
|
(groupRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(mockGroups[0]);
|
|
|
|
|
const bundle = { ...validBundle, groups: [{ name: 'dev-team', description: 'Devs', memberEmails: [] }] };
|
|
|
|
|
const result = await restoreService.restore(bundle, { conflictStrategy: 'skip' });
|
|
|
|
|
|
|
|
|
|
expect(result.groupsSkipped).toBe(1);
|
|
|
|
|
expect(result.groupsCreated).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('skips existing rbac bindings', async () => {
|
|
|
|
|
(rbacRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(mockRbacDefinitions[0]);
|
|
|
|
|
const bundle = { ...validBundle, rbacBindings: [{ name: 'admins', subjects: [], roleBindings: [] }] };
|
|
|
|
|
const result = await restoreService.restore(bundle, { conflictStrategy: 'skip' });
|
|
|
|
|
|
|
|
|
|
expect(result.rbacSkipped).toBe(1);
|
|
|
|
|
expect(result.rbacCreated).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 05:40:46 +00:00
|
|
|
it('aborts on conflict with fail strategy', async () => {
|
|
|
|
|
(serverRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(mockServers[0]);
|
|
|
|
|
const result = await restoreService.restore(validBundle, { conflictStrategy: 'fail' });
|
|
|
|
|
|
|
|
|
|
expect(result.errors).toContain('Server "github" already exists');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('overwrites existing with overwrite strategy', async () => {
|
|
|
|
|
(serverRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(mockServers[0]);
|
|
|
|
|
const result = await restoreService.restore(validBundle, { conflictStrategy: 'overwrite' });
|
|
|
|
|
|
|
|
|
|
expect(result.serversCreated).toBe(1);
|
|
|
|
|
expect(serverRepo.update).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
it('overwrites existing rbac bindings', async () => {
|
|
|
|
|
(rbacRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(mockRbacDefinitions[0]);
|
|
|
|
|
const bundle = {
|
|
|
|
|
...validBundle,
|
|
|
|
|
rbacBindings: [{ name: 'admins', subjects: [{ kind: 'User', name: 'new@test.com' }], roleBindings: [{ role: 'view', resource: 'servers' }] }],
|
|
|
|
|
};
|
|
|
|
|
const result = await restoreService.restore(bundle, { conflictStrategy: 'overwrite' });
|
|
|
|
|
|
|
|
|
|
expect(result.rbacCreated).toBe(1);
|
|
|
|
|
expect(rbacRepo.update).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 05:40:46 +00:00
|
|
|
it('fails restore with encrypted bundle and no password', async () => {
|
|
|
|
|
const encBundle = { ...validBundle, encrypted: true, encryptedSecrets: encrypt('{}', 'pw') };
|
|
|
|
|
const result = await restoreService.restore(encBundle);
|
|
|
|
|
expect(result.errors).toContain('Backup is encrypted but no password provided');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('restores encrypted bundle with correct password', async () => {
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const encryptedData = { 'secret:github-secrets:GITHUB_TOKEN': 'ghp_decrypted' };
|
2026-02-21 05:40:46 +00:00
|
|
|
const encBundle = {
|
|
|
|
|
...validBundle,
|
|
|
|
|
encrypted: true,
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
encryptedSecrets: encrypt(JSON.stringify(encryptedData), 'test-pw'),
|
|
|
|
|
secrets: [{ name: 'github-secrets', data: { GITHUB_TOKEN: '__ENCRYPTED:secret:github-secrets:GITHUB_TOKEN__' } }],
|
2026-02-21 05:40:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await restoreService.restore(encBundle, { password: 'test-pw' });
|
|
|
|
|
expect(result.errors).toHaveLength(0);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(result.secretsCreated).toBe(1);
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('fails with wrong decryption password', async () => {
|
|
|
|
|
const encBundle = {
|
|
|
|
|
...validBundle,
|
|
|
|
|
encrypted: true,
|
|
|
|
|
encryptedSecrets: encrypt('{"key":"val"}', 'correct'),
|
|
|
|
|
};
|
|
|
|
|
const result = await restoreService.restore(encBundle, { password: 'wrong' });
|
|
|
|
|
expect(result.errors[0]).toContain('Failed to decrypt');
|
|
|
|
|
});
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
|
|
|
|
|
it('restores in correct order: secrets → servers → users → groups → projects → rbac', async () => {
|
|
|
|
|
const callOrder: string[] = [];
|
|
|
|
|
(secretRepo.create as ReturnType<typeof vi.fn>).mockImplementation(async () => { callOrder.push('secret'); return { id: 'sec' }; });
|
|
|
|
|
(serverRepo.create as ReturnType<typeof vi.fn>).mockImplementation(async () => { callOrder.push('server'); return { id: 'srv' }; });
|
|
|
|
|
(userRepo.create as ReturnType<typeof vi.fn>).mockImplementation(async () => { callOrder.push('user'); return { id: 'usr' }; });
|
|
|
|
|
(groupRepo.create as ReturnType<typeof vi.fn>).mockImplementation(async () => { callOrder.push('group'); return { id: 'grp' }; });
|
2026-02-23 17:50:01 +00:00
|
|
|
(projectRepo.create as ReturnType<typeof vi.fn>).mockImplementation(async () => { callOrder.push('project'); return { id: 'proj', servers: [] }; });
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
(rbacRepo.create as ReturnType<typeof vi.fn>).mockImplementation(async () => { callOrder.push('rbac'); return { id: 'rbac' }; });
|
|
|
|
|
|
|
|
|
|
await restoreService.restore(fullBundle);
|
|
|
|
|
|
|
|
|
|
expect(callOrder[0]).toBe('secret');
|
|
|
|
|
expect(callOrder[1]).toBe('server');
|
|
|
|
|
expect(callOrder[2]).toBe('user');
|
|
|
|
|
expect(callOrder[3]).toBe('user'); // second user
|
|
|
|
|
expect(callOrder[4]).toBe('group');
|
|
|
|
|
expect(callOrder[5]).toBe('project');
|
|
|
|
|
expect(callOrder[6]).toBe('rbac');
|
|
|
|
|
});
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Backup Routes', () => {
|
|
|
|
|
let backupService: BackupService;
|
|
|
|
|
let restoreService: RestoreService;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
const sRepo = mockServerRepo();
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const secRepo = mockSecretRepo();
|
2026-02-21 05:40:46 +00:00
|
|
|
const prRepo = mockProjectRepo();
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
backupService = new BackupService(sRepo, prRepo, secRepo, mockUserRepo(), mockGroupRepo(), mockRbacRepo());
|
2026-02-21 05:40:46 +00:00
|
|
|
|
|
|
|
|
const rSRepo = mockServerRepo();
|
|
|
|
|
(rSRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const rSecRepo = mockSecretRepo();
|
|
|
|
|
(rSecRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
2026-02-21 05:40:46 +00:00
|
|
|
const rPrRepo = mockProjectRepo();
|
|
|
|
|
(rPrRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
const rUserRepo = mockUserRepo();
|
|
|
|
|
(rUserRepo.findByEmail as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
const rGroupRepo = mockGroupRepo();
|
|
|
|
|
(rGroupRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
const rRbacRepo = mockRbacRepo();
|
|
|
|
|
(rRbacRepo.findByName as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
|
|
|
restoreService = new RestoreService(rSRepo, rPrRepo, rSecRepo, rUserRepo, rGroupRepo, rRbacRepo);
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
async function buildApp() {
|
|
|
|
|
const app = Fastify();
|
|
|
|
|
registerBackupRoutes(app, { backupService, restoreService });
|
|
|
|
|
return app;
|
|
|
|
|
}
|
|
|
|
|
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
it('POST /api/v1/backup returns bundle with new resource types', async () => {
|
2026-02-21 05:40:46 +00:00
|
|
|
const app = await buildApp();
|
|
|
|
|
const res = await app.inject({
|
|
|
|
|
method: 'POST',
|
|
|
|
|
url: '/api/v1/backup',
|
|
|
|
|
payload: {},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
|
|
|
const body = res.json();
|
|
|
|
|
expect(body.version).toBe('1');
|
|
|
|
|
expect(body.servers).toBeDefined();
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
expect(body.secrets).toBeDefined();
|
2026-02-21 05:40:46 +00:00
|
|
|
expect(body.projects).toBeDefined();
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
expect(body.users).toBeDefined();
|
|
|
|
|
expect(body.groups).toBeDefined();
|
|
|
|
|
expect(body.rbacBindings).toBeDefined();
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('POST /api/v1/restore imports bundle', async () => {
|
|
|
|
|
const app = await buildApp();
|
|
|
|
|
const bundle = await backupService.createBackup();
|
|
|
|
|
|
|
|
|
|
const res = await app.inject({
|
|
|
|
|
method: 'POST',
|
|
|
|
|
url: '/api/v1/restore',
|
|
|
|
|
payload: { bundle, conflictStrategy: 'skip' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
|
|
|
const body = res.json();
|
|
|
|
|
expect(body.serversCreated).toBeDefined();
|
feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:19 +00:00
|
|
|
expect(body.usersCreated).toBeDefined();
|
|
|
|
|
expect(body.groupsCreated).toBeDefined();
|
|
|
|
|
expect(body.rbacCreated).toBeDefined();
|
2026-02-21 05:40:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('POST /api/v1/restore rejects invalid bundle', async () => {
|
|
|
|
|
const app = await buildApp();
|
|
|
|
|
const res = await app.inject({
|
|
|
|
|
method: 'POST',
|
|
|
|
|
url: '/api/v1/restore',
|
|
|
|
|
payload: { bundle: { invalid: true } },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(res.statusCode).toBe(400);
|
|
|
|
|
expect(res.json().error).toContain('Invalid');
|
|
|
|
|
});
|
|
|
|
|
});
|