mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-18 03:12:16 +08:00
699 lines
21 KiB
TypeScript
699 lines
21 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import { LEGACY_PERSIST_KEY } from './legacyConnectionStorage';
|
|
import {
|
|
bootstrapSecureConfig,
|
|
finalizeSecurityUpdateStatus,
|
|
mergeSecurityUpdateStatusWithLegacySource,
|
|
startSecurityUpdateFromBootstrap,
|
|
} from './secureConfigBootstrap';
|
|
import { stripLegacyPersistedConnectionById } from './legacyConnectionStorage';
|
|
|
|
const legacyPayload = JSON.stringify({
|
|
state: {
|
|
connections: [
|
|
{
|
|
id: 'legacy-1',
|
|
name: 'Legacy',
|
|
config: {
|
|
id: 'legacy-1',
|
|
type: 'postgres',
|
|
host: 'db.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
password: 'secret',
|
|
},
|
|
},
|
|
],
|
|
globalProxy: {
|
|
enabled: true,
|
|
type: 'http',
|
|
host: '127.0.0.1',
|
|
port: 8080,
|
|
user: 'ops',
|
|
password: 'proxy-secret',
|
|
},
|
|
},
|
|
});
|
|
|
|
const createMemoryStorage = () => {
|
|
const data = new Map<string, string>();
|
|
return {
|
|
getItem: (key: string) => data.get(key) ?? null,
|
|
setItem: (key: string, value: string) => {
|
|
data.set(key, value);
|
|
},
|
|
removeItem: (key: string) => {
|
|
data.delete(key);
|
|
},
|
|
};
|
|
};
|
|
|
|
const createBaseArgs = (storage = createMemoryStorage()) => {
|
|
const replaceConnections = vi.fn();
|
|
const replaceGlobalProxy = vi.fn();
|
|
|
|
storage.setItem(LEGACY_PERSIST_KEY, legacyPayload);
|
|
|
|
return {
|
|
storage,
|
|
replaceConnections,
|
|
replaceGlobalProxy,
|
|
};
|
|
};
|
|
|
|
describe('secureConfigBootstrap', () => {
|
|
it('builds legacy pending summary and issue list before the first round starts', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
...args,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'not_detected',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.status.overallStatus).toBe('pending');
|
|
expect(result.status.summary).toEqual({
|
|
total: 2,
|
|
updated: 0,
|
|
pending: 2,
|
|
skipped: 0,
|
|
failed: 0,
|
|
});
|
|
expect(result.status.issues).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({
|
|
scope: 'connection',
|
|
refId: 'legacy-1',
|
|
action: 'open_connection',
|
|
}),
|
|
expect.objectContaining({
|
|
scope: 'global_proxy',
|
|
action: 'open_proxy_settings',
|
|
}),
|
|
]));
|
|
});
|
|
|
|
it('shows intro when legacy sensitive items exist and backend status is pending', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
...args,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'pending',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.status.overallStatus).toBe('pending');
|
|
expect(result.shouldShowIntro).toBe(true);
|
|
expect(result.shouldShowBanner).toBe(false);
|
|
expect(args.replaceConnections).toHaveBeenCalledWith(
|
|
expect.arrayContaining([expect.objectContaining({ id: 'legacy-1' })]),
|
|
);
|
|
});
|
|
|
|
it('keeps banner flow without intro when backend status is postponed', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
...args,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'postponed',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.shouldShowIntro).toBe(false);
|
|
expect(result.shouldShowBanner).toBe(true);
|
|
});
|
|
|
|
it('keeps legacy pending summary and issues when a pre-start round is postponed', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
...args,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'postponed',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.status.overallStatus).toBe('postponed');
|
|
expect(result.status.summary.total).toBe(2);
|
|
expect(result.status.summary.pending).toBe(2);
|
|
expect(result.status.issues).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ scope: 'connection', refId: 'legacy-1' }),
|
|
expect.objectContaining({ scope: 'global_proxy' }),
|
|
]));
|
|
});
|
|
|
|
it('merges backend pending issues with legacy source items before the first round starts', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
...args,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'pending',
|
|
summary: { total: 1, updated: 0, pending: 1, skipped: 0, failed: 0 },
|
|
issues: [
|
|
{
|
|
id: 'ai-provider-openai-main',
|
|
scope: 'ai_provider',
|
|
refId: 'openai-main',
|
|
title: 'OpenAI',
|
|
severity: 'medium',
|
|
status: 'pending',
|
|
reasonCode: 'secret_missing',
|
|
action: 'open_ai_settings',
|
|
message: 'AI 提供商配置仍需完成安全更新',
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.status.overallStatus).toBe('pending');
|
|
expect(result.status.summary).toEqual({
|
|
total: 3,
|
|
updated: 0,
|
|
pending: 3,
|
|
skipped: 0,
|
|
failed: 0,
|
|
});
|
|
expect(result.status.issues).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ scope: 'ai_provider', refId: 'openai-main' }),
|
|
expect.objectContaining({ scope: 'connection', refId: 'legacy-1' }),
|
|
expect.objectContaining({ scope: 'global_proxy' }),
|
|
]));
|
|
});
|
|
|
|
it('keeps banner flow without intro when backend status is rolled_back', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
...args,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'rolled_back',
|
|
summary: { total: 1, updated: 0, pending: 0, skipped: 0, failed: 1 },
|
|
issues: [],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.shouldShowIntro).toBe(false);
|
|
expect(result.shouldShowBanner).toBe(true);
|
|
});
|
|
|
|
it('merges legacy pending items into rolled_back status without overwriting backend system issues', () => {
|
|
const status = mergeSecurityUpdateStatusWithLegacySource({
|
|
overallStatus: 'rolled_back',
|
|
summary: { total: 1, updated: 0, pending: 0, skipped: 0, failed: 1 },
|
|
issues: [
|
|
{
|
|
id: 'system-blocked',
|
|
scope: 'system',
|
|
title: '系统回滚',
|
|
severity: 'high',
|
|
status: 'failed',
|
|
reasonCode: 'environment_blocked',
|
|
action: 'view_details',
|
|
message: '后端已回滚本轮更新,需要处理后重试。',
|
|
},
|
|
],
|
|
}, legacyPayload);
|
|
|
|
expect(status.overallStatus).toBe('rolled_back');
|
|
expect(status.summary).toEqual({
|
|
total: 3,
|
|
updated: 0,
|
|
pending: 2,
|
|
skipped: 0,
|
|
failed: 1,
|
|
});
|
|
expect(status.issues).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: 'system-blocked', scope: 'system' }),
|
|
expect.objectContaining({ id: 'legacy-connection-legacy-1', scope: 'connection', refId: 'legacy-1' }),
|
|
expect.objectContaining({ id: 'legacy-global-proxy-default', scope: 'global_proxy' }),
|
|
]));
|
|
});
|
|
|
|
it('merges legacy pending items into needs_attention status without overwriting backend system issues', () => {
|
|
const status = mergeSecurityUpdateStatusWithLegacySource({
|
|
overallStatus: 'needs_attention',
|
|
summary: { total: 2, updated: 1, pending: 0, skipped: 0, failed: 1 },
|
|
issues: [
|
|
{
|
|
id: 'system-partial-failure',
|
|
scope: 'system',
|
|
title: '部分失败',
|
|
severity: 'high',
|
|
status: 'failed',
|
|
reasonCode: 'environment_blocked',
|
|
action: 'view_details',
|
|
message: '部分项目迁移失败,需要人工处理。',
|
|
},
|
|
{
|
|
id: 'ai-provider-openai-main',
|
|
scope: 'ai_provider',
|
|
refId: 'openai-main',
|
|
title: 'OpenAI',
|
|
severity: 'medium',
|
|
status: 'updated',
|
|
action: 'open_ai_settings',
|
|
message: 'AI 提供商配置已完成安全更新。',
|
|
},
|
|
],
|
|
}, legacyPayload);
|
|
|
|
expect(status.overallStatus).toBe('needs_attention');
|
|
expect(status.summary).toEqual({
|
|
total: 4,
|
|
updated: 1,
|
|
pending: 2,
|
|
skipped: 0,
|
|
failed: 1,
|
|
});
|
|
expect(status.issues).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: 'system-partial-failure', scope: 'system' }),
|
|
expect.objectContaining({ id: 'ai-provider-openai-main', scope: 'ai_provider', refId: 'openai-main' }),
|
|
expect.objectContaining({ id: 'legacy-connection-legacy-1', scope: 'connection', refId: 'legacy-1' }),
|
|
expect.objectContaining({ id: 'legacy-global-proxy-default', scope: 'global_proxy' }),
|
|
]));
|
|
});
|
|
|
|
it('does not merge local legacy pending items back into an active migration round that already reports needs_attention', () => {
|
|
const status = mergeSecurityUpdateStatusWithLegacySource({
|
|
migrationId: 'migration-active-1',
|
|
overallStatus: 'needs_attention',
|
|
summary: { total: 3, updated: 2, pending: 1, skipped: 0, failed: 0 },
|
|
issues: [
|
|
{
|
|
id: 'ai-provider-openai-main',
|
|
scope: 'ai_provider',
|
|
refId: 'openai-main',
|
|
title: 'OpenAI',
|
|
severity: 'medium',
|
|
status: 'needs_attention',
|
|
reasonCode: 'secret_missing',
|
|
action: 'open_ai_settings',
|
|
message: 'AI 提供商配置需要补充后才能完成安全更新。',
|
|
},
|
|
],
|
|
}, legacyPayload);
|
|
|
|
expect(status.overallStatus).toBe('needs_attention');
|
|
expect(status.summary).toEqual({
|
|
total: 3,
|
|
updated: 2,
|
|
pending: 1,
|
|
skipped: 0,
|
|
failed: 0,
|
|
});
|
|
expect(status.issues).toEqual([
|
|
expect.objectContaining({ id: 'ai-provider-openai-main', scope: 'ai_provider', refId: 'openai-main' }),
|
|
]);
|
|
});
|
|
|
|
it('does not merge local legacy pending items back into a rolled_back migration round', () => {
|
|
const status = mergeSecurityUpdateStatusWithLegacySource({
|
|
migrationId: 'migration-active-2',
|
|
overallStatus: 'rolled_back',
|
|
summary: { total: 3, updated: 1, pending: 0, skipped: 0, failed: 2 },
|
|
issues: [
|
|
{
|
|
id: 'system-blocked',
|
|
scope: 'system',
|
|
title: '系统回滚',
|
|
severity: 'high',
|
|
status: 'failed',
|
|
reasonCode: 'environment_blocked',
|
|
action: 'view_details',
|
|
message: '后端已回滚本轮更新,需要处理后重试。',
|
|
},
|
|
],
|
|
}, legacyPayload);
|
|
|
|
expect(status.overallStatus).toBe('rolled_back');
|
|
expect(status.summary).toEqual({
|
|
total: 3,
|
|
updated: 1,
|
|
pending: 0,
|
|
skipped: 0,
|
|
failed: 2,
|
|
});
|
|
expect(status.issues).toEqual([
|
|
expect.objectContaining({ id: 'system-blocked', scope: 'system' }),
|
|
]);
|
|
});
|
|
|
|
it('loads backend secure config directly when no legacy source exists', async () => {
|
|
const storage = createMemoryStorage();
|
|
const replaceConnections = vi.fn();
|
|
const replaceGlobalProxy = vi.fn();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
storage,
|
|
replaceConnections,
|
|
replaceGlobalProxy,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'not_detected',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
GetSavedConnections: vi.fn().mockResolvedValue([
|
|
{
|
|
id: 'secure-1',
|
|
name: 'Secure',
|
|
config: {
|
|
id: 'secure-1',
|
|
type: 'postgres',
|
|
host: 'db.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
},
|
|
},
|
|
]),
|
|
},
|
|
});
|
|
|
|
expect(result.status.overallStatus).toBe('not_detected');
|
|
expect(replaceConnections).toHaveBeenCalledWith(
|
|
expect.arrayContaining([expect.objectContaining({ id: 'secure-1' })]),
|
|
);
|
|
});
|
|
|
|
it('shows intro when backend status is pending even without legacy local source', async () => {
|
|
const storage = createMemoryStorage();
|
|
const replaceConnections = vi.fn();
|
|
const replaceGlobalProxy = vi.fn();
|
|
|
|
const result = await bootstrapSecureConfig({
|
|
storage,
|
|
replaceConnections,
|
|
replaceGlobalProxy,
|
|
backend: {
|
|
GetSecurityUpdateStatus: vi.fn().mockResolvedValue({
|
|
overallStatus: 'pending',
|
|
summary: { total: 1, updated: 0, pending: 1, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.status.overallStatus).toBe('pending');
|
|
expect(result.shouldShowIntro).toBe(true);
|
|
expect(result.shouldShowBanner).toBe(false);
|
|
});
|
|
|
|
it('falls back to legacy visible config when StartSecurityUpdate throws', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await startSecurityUpdateFromBootstrap({
|
|
...args,
|
|
backend: {
|
|
StartSecurityUpdate: vi.fn().mockRejectedValue(new Error('boom')),
|
|
},
|
|
});
|
|
|
|
expect(result.status).toBeNull();
|
|
expect(result.error?.message).toContain('boom');
|
|
expect(args.replaceConnections).toHaveBeenCalledWith(
|
|
expect.arrayContaining([expect.objectContaining({ id: 'legacy-1' })]),
|
|
);
|
|
expect(args.storage.getItem(LEGACY_PERSIST_KEY)).toContain('"password":"secret"');
|
|
});
|
|
|
|
it('starts security update even when rawPayload is empty but backend supports AI-only update', async () => {
|
|
const storage = createMemoryStorage();
|
|
const replaceConnections = vi.fn();
|
|
const replaceGlobalProxy = vi.fn();
|
|
const StartSecurityUpdate = vi.fn().mockResolvedValue({
|
|
overallStatus: 'completed',
|
|
summary: { total: 1, updated: 1, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
});
|
|
|
|
const result = await startSecurityUpdateFromBootstrap({
|
|
storage,
|
|
replaceConnections,
|
|
replaceGlobalProxy,
|
|
backend: {
|
|
StartSecurityUpdate,
|
|
},
|
|
});
|
|
|
|
expect(result.error).toBeNull();
|
|
expect(result.status?.overallStatus).toBe('completed');
|
|
expect(StartSecurityUpdate).toHaveBeenCalledWith({
|
|
sourceType: 'current_app_saved_config',
|
|
rawPayload: '',
|
|
options: {
|
|
allowPartial: true,
|
|
writeBackup: true,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('keeps source-side secrets when update ends in needs_attention', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await startSecurityUpdateFromBootstrap({
|
|
...args,
|
|
backend: {
|
|
StartSecurityUpdate: vi.fn().mockResolvedValue({
|
|
overallStatus: 'needs_attention',
|
|
summary: { total: 3, updated: 2, pending: 1, skipped: 0, failed: 0 },
|
|
issues: [{ id: 'ai-1' }],
|
|
}),
|
|
GetSavedConnections: vi.fn().mockResolvedValue([]),
|
|
},
|
|
});
|
|
|
|
expect(result.status?.overallStatus).toBe('needs_attention');
|
|
expect(args.storage.getItem(LEGACY_PERSIST_KEY)).toContain('"password":"secret"');
|
|
});
|
|
|
|
it('cleans source-side secrets only after completed update and backend refresh', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const result = await startSecurityUpdateFromBootstrap({
|
|
...args,
|
|
backend: {
|
|
StartSecurityUpdate: vi.fn().mockResolvedValue({
|
|
overallStatus: 'completed',
|
|
summary: { total: 3, updated: 3, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}),
|
|
GetSavedConnections: vi.fn().mockResolvedValue([
|
|
{
|
|
id: 'secure-1',
|
|
name: 'Secure',
|
|
config: {
|
|
id: 'secure-1',
|
|
type: 'postgres',
|
|
host: 'db.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
},
|
|
hasPrimaryPassword: true,
|
|
},
|
|
]),
|
|
GetGlobalProxyConfig: vi.fn().mockResolvedValue({
|
|
success: true,
|
|
data: {
|
|
enabled: true,
|
|
type: 'http',
|
|
host: '127.0.0.1',
|
|
port: 8080,
|
|
user: 'ops',
|
|
hasPassword: true,
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.status?.overallStatus).toBe('completed');
|
|
expect(args.storage.getItem(LEGACY_PERSIST_KEY)).not.toContain('"password":"secret"');
|
|
expect(args.replaceConnections).toHaveBeenLastCalledWith(
|
|
expect.arrayContaining([expect.objectContaining({ id: 'secure-1' })]),
|
|
);
|
|
});
|
|
|
|
it('refreshes backend config and strips source-side secrets when a later round finishes as completed', async () => {
|
|
const args = createBaseArgs();
|
|
|
|
const status = await finalizeSecurityUpdateStatus({
|
|
...args,
|
|
backend: {
|
|
GetSavedConnections: vi.fn().mockResolvedValue([
|
|
{
|
|
id: 'secure-1',
|
|
name: 'Secure',
|
|
config: {
|
|
id: 'secure-1',
|
|
type: 'postgres',
|
|
host: 'db.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
},
|
|
hasPrimaryPassword: true,
|
|
},
|
|
]),
|
|
GetGlobalProxyConfig: vi.fn().mockResolvedValue({
|
|
success: true,
|
|
data: {
|
|
enabled: true,
|
|
type: 'http',
|
|
host: '127.0.0.1',
|
|
port: 8080,
|
|
user: 'ops',
|
|
hasPassword: true,
|
|
},
|
|
}),
|
|
},
|
|
}, {
|
|
overallStatus: 'completed',
|
|
summary: { total: 3, updated: 3, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
});
|
|
|
|
expect(status.overallStatus).toBe('completed');
|
|
expect(args.storage.getItem(LEGACY_PERSIST_KEY)).not.toContain('"password":"secret"');
|
|
expect(args.replaceConnections).toHaveBeenLastCalledWith(
|
|
expect.arrayContaining([expect.objectContaining({ id: 'secure-1' })]),
|
|
);
|
|
});
|
|
|
|
it('reduces legacy pending issues after a single connection is repaired before the first round starts', () => {
|
|
const initialStatus = mergeSecurityUpdateStatusWithLegacySource({
|
|
overallStatus: 'not_detected',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}, legacyPayload);
|
|
const nextPayload = stripLegacyPersistedConnectionById(legacyPayload, 'legacy-1');
|
|
|
|
const status = mergeSecurityUpdateStatusWithLegacySource({
|
|
overallStatus: 'not_detected',
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
}, nextPayload, {
|
|
previousStatus: initialStatus,
|
|
});
|
|
|
|
expect(status.overallStatus).toBe('pending');
|
|
expect(status.summary).toEqual({
|
|
total: 2,
|
|
updated: 1,
|
|
pending: 1,
|
|
skipped: 0,
|
|
failed: 0,
|
|
});
|
|
expect(status.issues).toEqual([
|
|
expect.objectContaining({
|
|
scope: 'global_proxy',
|
|
action: 'open_proxy_settings',
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it('accumulates pre-start repaired progress across multiple connection saves in the same round-free session', () => {
|
|
const multiConnectionPayload = JSON.stringify({
|
|
state: {
|
|
connections: [
|
|
{
|
|
id: 'legacy-1',
|
|
name: 'Legacy 1',
|
|
config: {
|
|
id: 'legacy-1',
|
|
type: 'postgres',
|
|
host: 'db-1.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
password: 'secret-1',
|
|
},
|
|
},
|
|
{
|
|
id: 'legacy-2',
|
|
name: 'Legacy 2',
|
|
config: {
|
|
id: 'legacy-2',
|
|
type: 'postgres',
|
|
host: 'db-2.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
password: 'secret-2',
|
|
},
|
|
},
|
|
{
|
|
id: 'legacy-3',
|
|
name: 'Legacy 3',
|
|
config: {
|
|
id: 'legacy-3',
|
|
type: 'postgres',
|
|
host: 'db-3.local',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
password: 'secret-3',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const backendStatus = {
|
|
overallStatus: 'not_detected' as const,
|
|
summary: { total: 0, updated: 0, pending: 0, skipped: 0, failed: 0 },
|
|
issues: [],
|
|
};
|
|
const initialStatus = mergeSecurityUpdateStatusWithLegacySource(backendStatus, multiConnectionPayload);
|
|
const afterFirstRepairPayload = stripLegacyPersistedConnectionById(multiConnectionPayload, 'legacy-1');
|
|
const afterFirstRepairStatus = mergeSecurityUpdateStatusWithLegacySource(backendStatus, afterFirstRepairPayload, {
|
|
previousStatus: initialStatus,
|
|
});
|
|
const afterSecondRepairPayload = stripLegacyPersistedConnectionById(afterFirstRepairPayload, 'legacy-2');
|
|
|
|
const afterSecondRepairStatus = mergeSecurityUpdateStatusWithLegacySource(backendStatus, afterSecondRepairPayload, {
|
|
previousStatus: afterFirstRepairStatus,
|
|
});
|
|
|
|
expect(afterFirstRepairStatus.summary).toEqual({
|
|
total: 3,
|
|
updated: 1,
|
|
pending: 2,
|
|
skipped: 0,
|
|
failed: 0,
|
|
});
|
|
expect(afterSecondRepairStatus.summary).toEqual({
|
|
total: 3,
|
|
updated: 2,
|
|
pending: 1,
|
|
skipped: 0,
|
|
failed: 0,
|
|
});
|
|
expect(afterSecondRepairStatus.issues).toEqual([
|
|
expect.objectContaining({
|
|
id: 'legacy-connection-legacy-3',
|
|
scope: 'connection',
|
|
refId: 'legacy-3',
|
|
}),
|
|
]);
|
|
});
|
|
});
|