kubricate
Version:
A TypeScript framework for building reusable, type-safe Kubernetes infrastructure — without the YAML mess.
1,193 lines (1,191 loc) • 39.7 kB
JavaScript
"use strict";
var _vitest = /*#__PURE__*/require("vitest");
var _InMemoryConnector = /*#__PURE__*/require("../connectors/InMemoryConnector.js");
var _InMemoryProvider = /*#__PURE__*/require("../providers/InMemoryProvider.js");
var _SecretManager = /*#__PURE__*/require("../SecretManager.js");
var _SecretRegistry = /*#__PURE__*/require("../SecretRegistry.js");
var _SecretsOrchestrator = /*#__PURE__*/require("./SecretsOrchestrator.js");
/* eslint-disable @typescript-eslint/no-explicit-any */
(0, _vitest.describe)('SecretsOrchestrator', () => {
let mockSecretManager;
let mockProvider;
let orchestrator;
let mockLogger;
let options;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
mockProvider = {
name: 'kubernetes',
// This is critical
secretType: 'Kubricate.InMemory',
// Or your desired secretType
allowMerge: true,
getEffectIdentifier: effect => effect.value?.metadata?.name ?? 'no-id',
prepare: _vitest.vi.fn((name, value) => [{
providerName: 'kubernetes',
type: 'kubectl',
value: {
kind: 'Secret',
metadata: {
name: 'test-secret',
provider: 'kubernetes'
},
data: {
[name]: value
}
}
}]),
mergeSecrets: _vitest.vi.fn(effects => effects)
};
mockSecretManager = {
getSecrets: _vitest.vi.fn(() => ({
DB_PASS: {
name: 'DB_PASS',
connector: 'env',
provider: 'kubernetes'
}
})),
resolveConnector: _vitest.vi.fn(() => ({
getWorkingDir: () => undefined,
setWorkingDir: _vitest.vi.fn(),
load: _vitest.vi.fn(() => Promise.resolve()),
get: _vitest.vi.fn(() => 's3cr3t')
})),
resolveProvider: _vitest.vi.fn(() => mockProvider)
};
options = {
config: {
stacks: {
api: {
getSecretManagers: () => ({
main: mockSecretManager
})
}
},
secret: {
conflict: {
strategies: {
intraProvider: 'autoMerge',
crossProvider: 'error',
crossManager: 'error'
}
},
secretSpec: mockSecretManager
}
},
logger: mockLogger,
effectOptions: {
workingDir: '/project'
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
});
(0, _vitest.it)('should validate secrets without errors', async () => {
await (0, _vitest.expect)(orchestrator.validate()).resolves.not.toThrow();
(0, _vitest.expect)(mockSecretManager.resolveConnector).toHaveBeenCalled();
});
(0, _vitest.it)('should apply and return prepared effects', async () => {
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(effects[0].value.kind).toBe('Secret');
(0, _vitest.expect)(mockProvider.prepare).toHaveBeenCalled();
(0, _vitest.expect)(mockProvider.mergeSecrets).toHaveBeenCalled();
});
(0, _vitest.it)('should cache provider in resolveProviderByName()', () => {
const provider = orchestrator['resolveProviderByName']('kubernetes');
(0, _vitest.expect)(provider).toBe(mockProvider);
const cached = orchestrator['providerCache'].get('kubernetes');
(0, _vitest.expect)(cached).toBe(mockProvider);
});
(0, _vitest.it)('should throw if provider name is not found', () => {
(0, _vitest.expect)(() => orchestrator['resolveProviderByName']('not-exist')).toThrowError('[SecretsOrchestrator] Provider "not-exist" not found in any registered SecretManager');
});
});
(0, _vitest.describe)('SecretsOrchestrator Multi-Level Merge Strategy', () => {
let mockLogger;
let mockProvider;
let mockSecretManager;
let orchestrator;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
mockProvider = {
name: 'kubernetes',
// This is critical
secretType: 'Kubricate.InMemory',
// Or your desired secretType
allowMerge: true,
getEffectIdentifier: effect => effect.value?.metadata?.name ?? 'no-id',
prepare: _vitest.vi.fn((name, value) => [{
providerName: 'kubernetes',
type: 'kubectl',
value: {
kind: 'Secret',
metadata: {
name: 'merged-secret',
provider: 'kubernetes'
},
data: {
[name]: value
}
}
}]),
mergeSecrets: _vitest.vi.fn(effects => effects)
};
mockSecretManager = (name, secrets) => ({
getSecrets: _vitest.vi.fn(() => secrets),
resolveConnector: _vitest.vi.fn(() => ({
getWorkingDir: () => undefined,
setWorkingDir: _vitest.vi.fn(),
load: _vitest.vi.fn(() => Promise.resolve()),
get: _vitest.vi.fn(key => secrets[key].value)
})),
resolveProvider: _vitest.vi.fn(() => mockProvider)
});
});
(0, _vitest.it)('snapshots multiple secrets from the same manager', async () => {
const _mockSecretManager = mockSecretManager('svc', {
DB_USER: {
connector: 'env',
provider: 'kubernetes',
value: 'admin'
},
DB_PASS: {
connector: 'env',
provider: 'kubernetes',
value: 's3cr3t'
}
});
const stacks = {
stack1: {
getSecretManagers: () => ({
svc: _mockSecretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
conflict: {
strategies: {
intraProvider: 'autoMerge',
crossProvider: 'autoMerge',
crossManager: 'autoMerge'
}
},
secretSpec: _mockSecretManager
}
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toMatchSnapshot();
});
});
(0, _vitest.describe)('SecretsOrchestrator Advanced Merge Tests', () => {
let mockLogger;
let mockProvider;
let mockSecretManager;
let orchestrator;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
mockProvider = {
name: 'kubernetes',
// This is critical
secretType: 'Kubricate.InMemory',
// Or your desired secretType
allowMerge: true,
getEffectIdentifier: effect => effect.value?.metadata?.name ?? 'no-id',
prepare: _vitest.vi.fn((name, value) => [{
providerName: 'kubernetes',
type: 'kubectl',
value: {
kind: 'Secret',
metadata: {
name: 'test',
provider: 'kubernetes'
},
data: {
[name]: JSON.stringify(value)
}
}
}]),
mergeSecrets: _vitest.vi.fn(effects => effects)
};
mockSecretManager = (name, secrets) => ({
getSecrets: _vitest.vi.fn(() => secrets),
resolveConnector: _vitest.vi.fn(() => ({
getWorkingDir: () => undefined,
setWorkingDir: _vitest.vi.fn(),
load: _vitest.vi.fn(() => Promise.resolve()),
get: _vitest.vi.fn(key => secrets[key].value)
})),
resolveProvider: _vitest.vi.fn(() => mockProvider)
});
});
(0, _vitest.it)('includes correct SecretOrigin metadata and snapshots effect', async () => {
const _mockSecretManager = mockSecretManager('svc', {
API_KEY: {
connector: 'env',
provider: 'kubernetes',
value: '12345'
}
});
const stacks = {
stack1: {
getSecretManagers: () => ({
svc: _mockSecretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
conflict: {
strategies: {
intraProvider: 'autoMerge'
}
},
secretSpec: _mockSecretManager
}
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toMatchSnapshot();
(0, _vitest.expect)(mockProvider.prepare).toHaveBeenCalledWith('API_KEY', '12345');
});
});
(0, _vitest.describe)('SecretsOrchestrator intraProvider (Integration Tests)', () => {
let orchestrator;
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should throw on intraProvider conflict with strategy "error"', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'value1',
SHARED_KEY_DUPLICATE: 'value2'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).addSecret({
name: 'SHARED_KEY',
provider: 'InMemoryProvider'
}).addSecret({
name: 'SHARED_KEY_DUPLICATE',
provider: 'InMemoryProvider'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
intraProvider: 'error' // This will trigger the error
}
}
}
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
await (0, _vitest.expect)(orchestrator.apply()).rejects.toThrowError(/\[conflict:error:intraProvider\]/);
});
(0, _vitest.it)('merges secrets within same provider when intraProvider is "autoMerge"', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
API_KEY: 'abc123',
API_SECRET: 'shhh'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).addSecret({
name: 'API_KEY',
provider: 'InMemoryProvider'
}).addSecret({
name: 'API_SECRET',
provider: 'InMemoryProvider'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
intraProvider: 'autoMerge'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(effects[0].value.storeName).toBe('secret-application');
(0, _vitest.expect)(effects[0].value.rawData).toEqual({
API_KEY: 'abc123',
API_SECRET: 'shhh'
});
});
(0, _vitest.it)('overwrites previous secret when intraProvider is "overwrite"', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'value1',
SHARED_KEY_DUPLICATE: 'value2'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).addSecret({
name: 'SHARED_KEY',
provider: 'InMemoryProvider'
}).addSecret({
name: 'SHARED_KEY_DUPLICATE',
provider: 'InMemoryProvider'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
intraProvider: 'overwrite'
}
}
}
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
const effect = effects[0];
// Should only contain last key (SHARED_KEY_DUPLICATE)
(0, _vitest.expect)(effect.value.rawData).toEqual({
SHARED_KEY_DUPLICATE: 'value2'
});
(0, _vitest.expect)(mockLogger.warn).toHaveBeenCalledWith(_vitest.expect.stringContaining('[conflict:overwrite:intraProvider]'));
});
(0, _vitest.it)('keeps last secret and logs dropped in overwrite strategy (intraProvider)', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'v1',
SHARED_KEY_DUPLICATE: 'v2'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'secret-app'
})).addSecret({
name: 'SHARED_KEY',
provider: 'InMemoryProvider'
}).addSecret({
name: 'SHARED_KEY_DUPLICATE',
provider: 'InMemoryProvider'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
intraProvider: 'overwrite'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(mockLogger.warn).toHaveBeenCalledWith(_vitest.expect.stringContaining('[conflict:overwrite:intraProvider]'));
});
});
(0, _vitest.describe)('SecretsOrchestrator crossProvider (Integration Tests)', () => {
let orchestrator;
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should throw on intraProvider conflict with strategy "error"', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
my_app_key: 'my_app_key_value',
my_app_key_2: 'my_app_key_2_value'
})).addProvider('InMemoryProvider1', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).addProvider('InMemoryProvider2', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).setDefaultProvider('InMemoryProvider1').addSecret({
name: 'my_app_key',
provider: 'InMemoryProvider1'
}).addSecret({
name: 'my_app_key_2',
provider: 'InMemoryProvider2'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
crossProvider: 'error'
}
}
}
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
await (0, _vitest.expect)(orchestrator.apply()).rejects.toThrowError(/\[conflict:error:crossProvider\]/);
});
(0, _vitest.it)('merges secrets across providers with same storeName when crossProvider is "autoMerge"', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
my_app_key: 'my_app_key_value',
my_app_key_2: 'my_app_key_2_value'
})).addProvider('InMemoryProvider1', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).addProvider('InMemoryProvider2', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).setDefaultProvider('InMemoryProvider1').addSecret({
name: 'my_app_key',
provider: 'InMemoryProvider1'
}).addSecret({
name: 'my_app_key_2',
provider: 'InMemoryProvider2'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
crossProvider: 'autoMerge'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1); // ✅ only one merged result
const effect = effects[0];
(0, _vitest.expect)(effect.providerName).toBe('InMemoryProvider1'); // or InMemoryProvider2, doesn't matter
(0, _vitest.expect)(effect.value.storeName).toBe('secret-application');
(0, _vitest.expect)(effect.value.rawData).toEqual({
my_app_key: 'my_app_key_value',
my_app_key_2: 'my_app_key_2_value'
});
});
(0, _vitest.it)('overwrites previous secret when crossProvider is "overwrite"', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'one',
SHARED_KEY_2: 'two'
})).addProvider('InMemoryProvider1', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).addProvider('InMemoryProvider2', new _InMemoryProvider.InMemoryProvider({
name: 'secret-application'
})).setDefaultProvider('InMemoryProvider1').addSecret({
name: 'SHARED_KEY',
provider: 'InMemoryProvider1'
}).addSecret({
name: 'SHARED_KEY_2',
provider: 'InMemoryProvider2'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
crossProvider: 'overwrite'
}
}
}
}
};
orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(effects[0].value.rawData).toEqual({
SHARED_KEY_2: 'two'
});
(0, _vitest.expect)(mockLogger.warn).toHaveBeenCalledWith(_vitest.expect.stringContaining('[conflict:overwrite:crossProvider]'));
});
(0, _vitest.it)('keeps last secret and logs dropped in overwrite strategy (crossProvider)', async () => {
const secretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'v1',
SHARED_KEY_DUPLICATE: 'v2'
})).addProvider('InMemoryProvider1', new _InMemoryProvider.InMemoryProvider({
name: 'secret-app'
})).addProvider('InMemoryProvider2', new _InMemoryProvider.InMemoryProvider({
name: 'secret-app'
})).setDefaultProvider('InMemoryProvider1').addSecret({
name: 'SHARED_KEY',
provider: 'InMemoryProvider1'
}).addSecret({
name: 'SHARED_KEY_DUPLICATE',
provider: 'InMemoryProvider2'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
default: secretManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretManager,
conflict: {
strategies: {
crossProvider: 'overwrite'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(mockLogger.warn).toHaveBeenCalledWith(_vitest.expect.stringContaining('[conflict:overwrite:crossProvider]'));
});
});
(0, _vitest.describe)('SecretsOrchestrator crossManager (Integration Tests)', () => {
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should throw on crossManager conflict with strategy "error"', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY: 'A'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared'
})).addSecret({
name: 'KEY',
provider: 'InMemoryProvider'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY: 'B'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared'
})).addSecret({
name: 'KEY',
provider: 'InMemoryProvider'
}).build();
const stack = {
getSecretManagers: () => ({
svc1,
svc2
})
};
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: {
app: stack
},
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'error'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
await (0, _vitest.expect)(orchestrator.apply()).rejects.toThrowError(/\[conflict:error:crossManager]/);
});
(0, _vitest.it)('merges on crossManager conflict with strategy "autoMerge"', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY1: 'A'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared'
})).addSecret({
name: 'KEY1',
provider: 'InMemoryProvider'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY2: 'B'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared'
})).addSecret({
name: 'KEY2',
provider: 'InMemoryProvider'
}).build();
const stack = {
getSecretManagers: () => ({
svc1,
svc2
})
};
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: {
app: stack
},
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'autoMerge'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(effects[0].value.rawData).toEqual({
KEY1: 'A',
KEY2: 'B'
});
});
(0, _vitest.it)('should overwrite secret when conflict occurs within the same SecretManager (crossManager)', async () => {
const a = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'one'
})).addProvider('P', new _InMemoryProvider.InMemoryProvider({
name: 'stack-secret'
})).addSecret({
name: 'SHARED_KEY',
provider: 'P'
}).build();
const b = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
SHARED_KEY: 'two'
})).addProvider('P', new _InMemoryProvider.InMemoryProvider({
name: 'stack-secret'
})).addSecret({
name: 'SHARED_KEY',
provider: 'P'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
a,
b
})
}
};
const secretRegistry = new _SecretRegistry.SecretRegistry().add('a', a).add('b', b);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'overwrite'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
});
(0, _vitest.it)('merges same identifier across different SecretManagers with strategy "overwrite"', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY: 'one'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-app'
})).addSecret({
name: 'KEY'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY: 'two'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-app'
})).addSecret({
name: 'KEY'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
svc1,
svc2
})
}
};
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'overwrite'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(mockLogger.warn).toHaveBeenCalledWith(_vitest.expect.stringContaining('[conflict:overwrite:crossManager]'));
});
});
(0, _vitest.describe)('SecretsOrchestrator crossManager (Integration Tests)', () => {
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('throws conflict when same identifier across managers and strategy = "error"', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemory', new _InMemoryConnector.InMemoryConnector({
KEY: 'value1'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemory', new _InMemoryConnector.InMemoryConnector({
KEY: 'value2'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY'
}).build();
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: {
app: {
getSecretManagers: () => ({
svc1,
svc2
})
}
},
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'error'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
await (0, _vitest.expect)(orchestrator.apply()).rejects.toThrowError(/\[conflict:error:crossManager]/);
});
(0, _vitest.it)('overwrites when same identifier across managers and strategy = "overwrite"', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemory', new _InMemoryConnector.InMemoryConnector({
KEY: 'value1'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemory', new _InMemoryConnector.InMemoryConnector({
KEY: 'value2'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY'
}).build();
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: {
app: {
getSecretManagers: () => ({
svc1,
svc2
})
}
},
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'overwrite'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(mockLogger.warn).toHaveBeenCalledWith(_vitest.expect.stringContaining('[conflict:overwrite:crossManager]'));
});
(0, _vitest.it)('auto-merges when different keys across managers and strategy = "autoMerge"', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemory', new _InMemoryConnector.InMemoryConnector({
KEY1: 'one'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY1'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemory', new _InMemoryConnector.InMemoryConnector({
KEY2: 'two'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY2'
}).build();
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: {
app: {
getSecretManagers: () => ({
svc1,
svc2
})
}
},
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'autoMerge'
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(effects[0].value.rawData).toEqual({
KEY1: 'one',
KEY2: 'two'
});
});
});
(0, _vitest.describe)('SecretsOrchestrator cross-stack using single SecretManager (Integration)', () => {
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should not conflict when multiple stacks share the same SecretManager', async () => {
const sharedManager = new _SecretManager.SecretManager().addConnector('InMemoryLoader', new _InMemoryConnector.InMemoryConnector({
API_KEY: 'abc123',
DB_PASSWORD: 'secretpw'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'app-secret'
})).setDefaultProvider('InMemoryProvider').addSecret({
name: 'API_KEY',
provider: 'InMemoryProvider'
}).addSecret({
name: 'DB_PASSWORD',
provider: 'InMemoryProvider'
}).build();
const stacks = {
frontend: {
getSecretManagers: () => ({
default: sharedManager
})
},
backend: {
getSecretManagers: () => ({
default: sharedManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: sharedManager
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
const effects = await orchestrator.apply();
(0, _vitest.expect)(effects).toHaveLength(1);
(0, _vitest.expect)(effects[0].providerName).toBe('InMemoryProvider');
(0, _vitest.expect)(effects[0].value.rawData).toEqual({
API_KEY: 'abc123',
DB_PASSWORD: 'secretpw'
});
// Should not log any conflict or error
(0, _vitest.expect)(mockLogger.warn).not.toHaveBeenCalled();
});
});
(0, _vitest.describe)('SecretsOrchestrator strictConflictMode (Negative Test)', () => {
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should throw if strictConflictMode=true but conflict strategies are relaxed', async () => {
const sharedManager = new _SecretManager.SecretManager().addConnector('InMemoryLoader', new _InMemoryConnector.InMemoryConnector({
API_KEY: 'abc123',
DB_PASSWORD: 'secretpw'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'app-secret'
})).setDefaultProvider('InMemoryProvider').addSecret({
name: 'API_KEY',
provider: 'InMemoryProvider'
}).addSecret({
name: 'DB_PASSWORD',
provider: 'InMemoryProvider'
}).build();
const stacks = {
frontend: {
getSecretManagers: () => ({
default: sharedManager
})
},
backend: {
getSecretManagers: () => ({
default: sharedManager
})
}
};
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
manager: sharedManager,
conflict: {
strict: true,
strategies: {
intraProvider: 'autoMerge' // ❌ should be error when strictConflictMode = true
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
// 💥 Expect an error thrown during validation, not apply phase
await (0, _vitest.expect)(orchestrator.apply()).rejects.toThrowError(/\[config:strictConflictMode\]/);
});
});
(0, _vitest.describe)('SecretsOrchestrator Cross-Manager Conflict Detection', () => {
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should detect conflict between different managers when strategy is error (needs managerName in conflictKey)', async () => {
const svc1 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY: 'one'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY'
}).build();
const svc2 = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
KEY: 'two'
})).addProvider('InMemoryProvider', new _InMemoryProvider.InMemoryProvider({
name: 'shared-secret'
})).addSecret({
name: 'KEY'
}).build();
const stacks = {
app: {
getSecretManagers: () => ({
svc1,
svc2
})
}
};
const secretRegistry = new _SecretRegistry.SecretRegistry().add('svc1', svc1).add('svc2', svc2);
const options = {
logger: mockLogger,
effectOptions: {},
config: {
stacks: stacks,
secret: {
secretSpec: secretRegistry,
conflict: {
strategies: {
crossManager: 'error' // ❗ สำคัญ
}
}
}
}
};
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create(options);
await (0, _vitest.expect)(orchestrator.apply()).rejects.toThrowError(/\[conflict:error:crossManager] Duplicate resource identifier/);
});
});
(0, _vitest.describe)('SecretsOrchestrator - Multiple SecretManagers with Same Provider Name', () => {
let mockLogger;
(0, _vitest.beforeEach)(() => {
mockLogger = {
info: _vitest.vi.fn(),
warn: _vitest.vi.fn(),
debug: _vitest.vi.fn()
};
});
(0, _vitest.it)('should apply secrets correctly without conflict', async () => {
// Arrange - Setup SecretManagers and Registry manually
const frontendSecretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
frontend_app_key: 'value1'
})).addProvider('OpaqueSecretProvider', new _InMemoryProvider.InMemoryProvider({
name: 'secret-frontend'
})).addSecret({
name: 'frontend_app_key'
});
const backendSecretManager = new _SecretManager.SecretManager().addConnector('InMemoryConnector', new _InMemoryConnector.InMemoryConnector({
backend_app_key: 'value2'
})).addProvider('OpaqueSecretProvider', new _InMemoryProvider.InMemoryProvider({
name: 'secret-backend'
})).addSecret({
name: 'backend_app_key'
});
const secretRegistry = new _SecretRegistry.SecretRegistry().add('frontend', frontendSecretManager).add('backend', backendSecretManager);
const orchestrator = _SecretsOrchestrator.SecretsOrchestrator.create({
effectOptions: {},
config: {
secret: {
secretSpec: secretRegistry
},
stacks: {}
},
logger: mockLogger // or mock logger if you want
});
// Act
const effects = await orchestrator.apply();
// Assert
(0, _vitest.expect)(effects).toHaveLength(2);
const metadata = effects.map(effect => {
return effect.value;
});
(0, _vitest.expect)(metadata).toStrictEqual([{
rawData: {
frontend_app_key: 'value1'
},
storeName: 'secret-frontend'
}, {
rawData: {
backend_app_key: 'value2'
},
storeName: 'secret-backend'
}]);
});
});
//# sourceMappingURL=SecretsOrchestrator.test.js.map