diff --git a/.changeset/happy-emus-cough.md b/.changeset/happy-emus-cough.md new file mode 100644 index 00000000..f8b2823a --- /dev/null +++ b/.changeset/happy-emus-cough.md @@ -0,0 +1,16 @@ +--- +'@aziontech/config': minor +--- + +feat: add optional versionId support across config + +- add `versionId` (config) <-> `version_id` (manifest) round-trip + handling to the application, connector, functions, firewall, network + list, waf and workload process-config strategies +- fix missing/broken `versionId` mapping in the connector and network + list strategies, where the manifest -> config direction silently + dropped or misnamed the field +- add `versionId` to the corresponding JSON schemas +- add test coverage for the new field, plus full test suites for the + previously untested application, cache, device groups, function + instances and rules strategies diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.test.ts new file mode 100644 index 00000000..423cb57a --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.test.ts @@ -0,0 +1,387 @@ +import { AzionConfig } from '../../../../types'; +import ApplicationProcessConfigStrategy from './applicationProcessConfigStrategy'; + +describe('ApplicationProcessConfigStrategy', () => { + let strategy: ApplicationProcessConfigStrategy; + + beforeEach(() => { + strategy = new ApplicationProcessConfigStrategy(); + }); + + describe('transformToManifest', () => { + it('should return empty array when no applications are provided', () => { + const config: AzionConfig = {}; + const result = strategy.transformToManifest(config); + expect(result).toEqual([]); + }); + + it('should return empty array when applications array is empty', () => { + const config: AzionConfig = { applications: [] }; + const result = strategy.transformToManifest(config); + expect(result).toEqual([]); + }); + + it('should transform a minimal application using default module values', () => { + const config: AzionConfig = { + applications: [{ name: 'my-app' }], + }; + + const result = strategy.transformToManifest(config); + + expect(result).toEqual([ + { + name: 'my-app', + active: true, + debug: false, + modules: { + cache: { enabled: true }, + functions: { enabled: false }, + application_accelerator: { enabled: true }, + image_processor: { enabled: false }, + }, + }, + ]); + }); + + it('should transform an application with explicit module flags', () => { + const config: AzionConfig = { + applications: [ + { + name: 'my-app', + active: false, + debug: true, + edgeCacheEnabled: false, + functionsEnabled: true, + applicationAcceleratorEnabled: false, + imageProcessorEnabled: true, + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result).toEqual([ + { + name: 'my-app', + active: false, + debug: true, + modules: { + cache: { enabled: false }, + functions: { enabled: true }, + application_accelerator: { enabled: false }, + image_processor: { enabled: true }, + }, + }, + ]); + }); + + it('should enable the functions module when functionsInstances is present even if functionsEnabled is not set', () => { + const config: AzionConfig = { + functions: [{ name: 'my-function', path: './functions/my-function.js' }], + applications: [ + { + name: 'my-app', + functionsInstances: [{ name: 'my-instance', ref: 'my-function' }], + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result[0].modules.functions.enabled).toBe(true); + }); + + it('should include cache_settings using the cache strategy when cache is provided', () => { + const config: AzionConfig = { + applications: [ + { + name: 'my-app', + cache: [{ name: 'my-cache' }], + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result[0].cache_settings).toEqual([ + expect.objectContaining({ + name: 'my-cache', + }), + ]); + }); + + it('should include rules using the rules strategy when rules is provided', () => { + const config: AzionConfig = { + applications: [ + { + name: 'my-app', + rules: { + request: [ + { + name: 'my-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + }, + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result[0].rules).toEqual([ + { + phase: 'request', + rule: expect.objectContaining({ name: 'my-rule' }), + }, + ]); + }); + + it('should include device_groups using the device groups strategy when deviceGroups is provided', () => { + const config: AzionConfig = { + applications: [ + { + name: 'my-app', + deviceGroups: [{ name: 'mobile', userAgent: 'Mobile' }], + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result[0].device_groups).toEqual([{ name: 'mobile', user_agent: 'Mobile' }]); + }); + + it('should include functions_instances using the function instances strategy when functionsInstances is provided', () => { + const config: AzionConfig = { + functions: [{ name: 'my-function', path: './functions/my-function.js' }], + applications: [ + { + name: 'my-app', + functionsInstances: [{ name: 'my-instance', ref: 'my-function' }], + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result[0].functions_instances).toEqual([ + { name: 'my-instance', function: 'my-function', args: {}, active: true }, + ]); + }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + applications: [{ name: 'app-with-version', versionId: 'version-abc-123' }], + }; + const configWithoutVersionId: AzionConfig = { + applications: [{ name: 'app-without-version' }], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId); + const resultWithout = strategy.transformToManifest(configWithoutVersionId); + + expect(resultWith[0].version_id).toBe('version-abc-123'); + expect(resultWithout[0].version_id).toBeUndefined(); + }); + + it('should transform multiple applications', () => { + const config: AzionConfig = { + applications: [{ name: 'app-1' }, { name: 'app-2' }], + }; + + const result = strategy.transformToManifest(config); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe('app-1'); + expect(result[1].name).toBe('app-2'); + }); + }); + + describe('transformToConfig', () => { + it('should return empty array when no applications are provided', () => { + const payload = {}; + const transformedPayload: AzionConfig = {}; + const result = strategy.transformToConfig(payload, transformedPayload); + expect(result).toEqual([]); + expect(transformedPayload.applications).toEqual([]); + }); + + it('should return empty array when applications array is empty', () => { + const payload = { applications: [] }; + const transformedPayload: AzionConfig = {}; + const result = strategy.transformToConfig(payload, transformedPayload); + expect(result).toEqual([]); + }); + + it('should transform a single application from manifest to config format', () => { + const payload = { + applications: [ + { + name: 'my-app', + active: true, + debug: false, + modules: { + cache: { enabled: true }, + functions: { enabled: false }, + application_accelerator: { enabled: true }, + image_processor: { enabled: false }, + }, + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result).toEqual([ + { + name: 'my-app', + active: true, + debug: false, + edgeCacheEnabled: true, + functionsEnabled: false, + applicationAcceleratorEnabled: true, + imageProcessorEnabled: false, + cache: undefined, + rules: undefined, + deviceGroups: undefined, + functionsInstances: undefined, + versionId: undefined, + }, + ]); + expect(transformedPayload.applications).toBe(result); + }); + + it('should transform cache_settings using the cache strategy when present', () => { + const payload = { + applications: [ + { + name: 'my-app', + modules: { cache: { enabled: true }, functions: { enabled: false } }, + cache_settings: [ + { + name: 'my-cache', + browser_cache: { behavior: 'honor', max_age: 0 }, + modules: { cache: {}, application_accelerator: {} }, + }, + ], + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result![0].cache).toEqual([expect.objectContaining({ name: 'my-cache' })]); + }); + + it('should transform rules using the rules strategy when present', () => { + const payload = { + applications: [ + { + name: 'my-app', + modules: { cache: { enabled: true }, functions: { enabled: false } }, + rules: [ + { + phase: 'request', + rule: { + name: 'my-rule', + active: true, + criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + }, + ], + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result![0].rules!.request).toEqual([expect.objectContaining({ name: 'my-rule' })]); + }); + + it('should transform device_groups using the device groups strategy when present', () => { + const payload = { + applications: [ + { + name: 'my-app', + modules: { cache: { enabled: true }, functions: { enabled: false } }, + device_groups: [{ name: 'mobile', user_agent: 'Mobile' }], + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result![0].deviceGroups).toEqual([{ name: 'mobile', userAgent: 'Mobile' }]); + }); + + it('should transform functions_instances using the function instances strategy when present', () => { + const payload = { + applications: [ + { + name: 'my-app', + modules: { cache: { enabled: true }, functions: { enabled: true } }, + functions_instances: [{ name: 'my-instance', function: 'my-function', args: {}, active: true }], + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result![0].functionsInstances).toEqual([ + { name: 'my-instance', ref: 'my-function', args: {}, active: true }, + ]); + }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + applications: [ + { + name: 'app-with-version', + modules: { cache: { enabled: true }, functions: { enabled: false } }, + version_id: 'version-abc-123', + }, + ], + }; + const payloadWithoutVersionId = { + applications: [ + { + name: 'app-without-version', + modules: { cache: { enabled: true }, functions: { enabled: false } }, + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId); + strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId); + + expect(transformedWithVersionId.applications![0].versionId).toBe('version-abc-123'); + expect(transformedWithoutVersionId.applications![0].versionId).toBeUndefined(); + }); + + it('should transform multiple applications', () => { + const payload = { + applications: [ + { name: 'app-1', modules: { cache: { enabled: true }, functions: { enabled: false } } }, + { name: 'app-2', modules: { cache: { enabled: true }, functions: { enabled: false } } }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result).toHaveLength(2); + expect(result![0].name).toBe('app-1'); + expect(result![1].name).toBe('app-2'); + }); + }); +}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.ts index c98fd8e5..ec8ae7ae 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy.ts @@ -58,6 +58,10 @@ class ApplicationProcessConfigStrategy extends ProcessConfigStrategy { ); } + if (app.versionId) { + application.version_id = app.versionId; + } + return application; }); } @@ -85,6 +89,7 @@ class ApplicationProcessConfigStrategy extends ProcessConfigStrategy { functionsInstances: app.functions_instances ? this.functionInstancesStrategy.transformToConfig(app.functions_instances) : undefined, + versionId: app.version_id, }; }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy_test_ts b/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy_test_ts deleted file mode 100644 index f760a7e1..00000000 --- a/packages/config/src/configProcessor/processStrategy/implementations/application/applicationProcessConfigStrategy_test_ts +++ /dev/null @@ -1,485 +0,0 @@ -// import { AzionConfig } from '../../../../types'; -// import ApplicationProcessConfigStrategy from './applicationProcessConfigStrategy'; -// import CacheProcessConfigStrategy from './cacheProcessConfigStrategy'; -// import DeviceGroupsProcessConfigStrategy from './deviceGroupsProcessConfigStrategy'; -// import FunctionInstancesProcessConfigStrategy from './functionInstancesProcessConfigStrategy'; -// import RulesProcessConfigStrategy from './rulesProcessConfigStrategy'; - -// // Mock the dependent strategies -// jest.mock('./cacheProcessConfigStrategy'); -// jest.mock('./rulesProcessConfigStrategy'); -// jest.mock('./deviceGroupsProcessConfigStrategy'); -// jest.mock('./functionInstancesProcessConfigStrategy'); - -// TODO: reviews tests -describe('ApplicationProcessConfigStrategy', () => { - it('should pass', () => { - expect(true).toBe(true); - }) -}) - -// describe('ApplicationProcessConfigStrategy', () => { -// let strategy: ApplicationProcessConfigStrategy; -// let mockCacheStrategy: jest.Mocked; -// let mockRulesStrategy: jest.Mocked; -// let mockDeviceGroupsStrategy: jest.Mocked; -// let mockFunctionInstancesStrategy: jest.Mocked; - -// beforeEach(() => { -// // Clear all mocks -// jest.clearAllMocks(); - -// // Setup mock implementations -// mockCacheStrategy = new CacheProcessConfigStrategy() as jest.Mocked; -// mockRulesStrategy = new RulesProcessConfigStrategy() as jest.Mocked; -// mockDeviceGroupsStrategy = new DeviceGroupsProcessConfigStrategy() as jest.Mocked; -// mockFunctionInstancesStrategy = new FunctionInstancesProcessConfigStrategy() as jest.Mocked; - -// // Initialize the mocks with default implementations -// (CacheProcessConfigStrategy as jest.Mock).mockImplementation(() => mockCacheStrategy); -// (RulesProcessConfigStrategy as jest.Mock).mockImplementation(() => mockRulesStrategy); -// (DeviceGroupsProcessConfigStrategy as jest.Mock).mockImplementation(() => mockDeviceGroupsStrategy); -// (FunctionInstancesProcessConfigStrategy as jest.Mock).mockImplementation(() => mockFunctionInstancesStrategy); - -// // Create the strategy instance -// strategy = new ApplicationProcessConfigStrategy(); -// }); - -// describe('transformToManifest', () => { -// it('should return empty array when no applications are provided', () => { -// const config: AzionConfig = {}; -// const result = strategy.transformToManifest(config); -// expect(result).toEqual([]); -// }); - -// it('should return empty array when applications array is empty', () => { -// const config: AzionConfig = { applications: [] }; -// const result = strategy.transformToManifest(config); -// expect(result).toEqual([]); -// }); - -// it('should transform a basic application configuration to manifest format with default values', () => { -// const config: AzionConfig = { -// applications: [ -// { -// name: 'test-application', -// }, -// ], -// }; - -// const result = strategy.transformToManifest(config); - -// expect(result).toEqual([ -// { -// name: 'test-application', -// active: true, -// debug: false, -// modules: { -// cache: { -// enabled: true, -// }, -// functions: { -// enabled: false, -// }, -// application_accelerator: { -// enabled: true, -// }, -// image_processor: { -// enabled: false, -// }, -// }, -// }, -// ]); -// }); - -// it('should transform a complete application configuration to manifest format', () => { -// // Mock return values for the strategy methods -// mockCacheStrategy.transformToManifest.mockReturnValue([{ -// name: 'test-cache', -// browser_cache: { behavior: 'override', max_age: 3600 }, -// modules: { -// cache: { -// behavior: 'override', -// max_age: 60, -// stale_cache: { enabled: false }, -// large_file_cache: { enabled: false, offset: 1024 }, -// tieredCache: { enabled: false, topology: 'near-edge' } -// }, -// application_accelerator: { -// cache_vary_by_method: [], -// cache_vary_by_querystring: { behavior: 'ignore', fields: [], sort_enabled: false }, -// cache_vary_by_cookies: { behavior: 'ignore', cookie_names: [] }, -// cache_vary_by_devices: { behavior: 'ignore', device_group: [] } -// } -// } -// }]); -// mockRulesStrategy.transformToManifest.mockReturnValue([{ -// phase: 'request', -// rule: { -// name: 'test-rule', -// active: true, -// criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'is_equal', argument: '/test' }]], -// behaviors: [{ type: 'deliver' }] -// } -// }]); -// mockDeviceGroupsStrategy.transformToManifest.mockReturnValue([{ -// name: 'test-device-group', -// user_agent: 'test-agent' -// }]); -// mockFunctionInstancesStrategy.transformToManifest.mockReturnValue([{ -// name: 'test-function-instance', -// function: 'test-function', -// args: {}, -// active: true -// }]); - -// const config: AzionConfig = { -// applications: [ -// { -// name: 'complete-application', -// active: false, -// debug: true, -// edgeCacheEnabled: false, -// functionsEnabled: true, -// applicationAcceleratorEnabled: false, -// imageProcessorEnabled: true, -// cache: [{ -// name: 'test-cache', -// browser: { maxAgeSeconds: 3600 }, -// tiered_cache: { -// enabled: true, -// topology: 'global' -// } -// }], -// rules: { request: [], response: [] }, -// deviceGroups: [{ name: 'test-device-group', userAgent: 'test-agent' }], -// functionsInstances: [{ name: 'test-function-instance', ref: 'test-function' }], -// }, -// ], -// functions: [{ name: 'test-function', path: './functions/test-function.js' }], -// connectors: [], -// }; - -// const result = strategy.transformToManifest(config); - -// expect(result).toEqual([ -// { -// name: 'complete-application', -// active: false, -// debug: true, -// modules: { -// cache: { -// enabled: false, -// tiered_cache: { -// enabled: true, -// topology: 'near-edge' -// } -// }, -// functions: { -// enabled: true, -// }, -// application_accelerator: { -// enabled: false, -// }, -// image_processor: { -// enabled: true, -// }, -// }, -// cache_settings: [{ -// name: 'test-cache', -// browser_cache: { behavior: 'override', max_age: 3600 }, -// modules: { -// cache: { -// behavior: 'override', -// max_age: 60, -// stale_cache: { enabled: false }, -// large_file_cache: { enabled: false, offset: 1024 }, -// tiered_cache: { enabled: false, topology: 'near-edge' } -// }, -// application_accelerator: { -// cache_vary_by_method: [], -// cache_vary_by_querystring: { behavior: 'ignore', fields: [], sort_enabled: false }, -// cache_vary_by_cookies: { behavior: 'ignore', cookie_names: [] }, -// cache_vary_by_devices: { behavior: 'ignore', device_group: [] } -// } -// } -// }], -// rules: [{ -// phase: 'request', -// rule: { -// name: 'test-rule', -// active: true, -// criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'is_equal', argument: '/test' }]], -// behaviors: [{ type: 'deliver' }] -// } -// }], -// device_groups: [{ -// name: 'test-device-group', -// user_agent: 'test-agent' -// }], -// functions_instances: [{ -// name: 'test-function-instance', -// function: 'test-function', -// args: {}, -// active: true -// }], -// }, -// ]); - -// // Verify that the strategy methods were called with the correct parameters -// expect(mockCacheStrategy.transformToManifest).toHaveBeenCalledWith([ -// { name: 'test-cache', browser: { maxAgeSeconds: 3600 } }, -// ]); -// expect(mockRulesStrategy.transformToManifest).toHaveBeenCalledWith( -// { request: [], response: [] }, -// [{ name: 'test-function', path: './functions/test-function.js' }], -// [] -// ); -// expect(mockDeviceGroupsStrategy.transformToManifest).toHaveBeenCalledWith([ -// { name: 'test-device-group', userAgent: 'test-agent' }, -// ]); -// expect(mockFunctionInstancesStrategy.transformToManifest).toHaveBeenCalledWith( -// [{ name: 'test-function-instance', ref: 'test-function' }], -// config -// ); -// }); - -// it('should handle functions enabled based on functionsInstances presence', () => { -// const config: AzionConfig = { -// applications: [ -// { -// name: 'app-with-function-instances', -// functionsInstances: [{ name: 'test-function-instance', ref: 'test-function' }], -// }, -// ], -// }; - -// mockFunctionInstancesStrategy.transformToManifest.mockReturnValue([{ -// name: 'test-function-instance', -// function: 'test-function', -// args: {}, -// active: true -// }]); - -// const result = strategy.transformToManifest(config); - -// expect(result[0].modules.functions.enabled).toBe(true); -// }); - -// it('should transform multiple applications to manifest format', () => { -// const config: AzionConfig = { -// applications: [ -// { -// name: 'app-1', -// }, -// { -// name: 'app-2', -// active: false, -// }, -// ], -// }; - -// const result = strategy.transformToManifest(config); - -// expect(result).toHaveLength(2); -// expect(result[0].name).toBe('app-1'); -// expect(result[1].name).toBe('app-2'); -// expect(result[0].active).toBe(true); -// expect(result[1].active).toBe(false); -// }); -// }); - -// describe('transformToConfig', () => { -// it('should return empty array when no applications are provided', () => { -// const payload = {}; -// const transformedPayload: AzionConfig = {}; - -// const result = strategy.transformToConfig(payload, transformedPayload); - -// expect(result).toEqual([]); -// expect(transformedPayload.applications).toEqual([]); -// }); - -// it('should return empty array when applications array is empty', () => { -// const payload = { applications: [] }; -// const transformedPayload: AzionConfig = {}; - -// const result = strategy.transformToConfig(payload, transformedPayload); - -// expect(result).toEqual([]); -// expect(transformedPayload.applications).toEqual([]); -// }); - -// it('should transform a basic application manifest to config format', () => { -// const payload = { -// applications: [ -// { -// name: 'test-application', -// active: true, -// debug: false, -// modules: { -// cache: { -// enabled: true, -// }, -// functions: { -// enabled: false, -// }, -// application_accelerator: { -// enabled: true, -// }, -// image_processor: { -// enabled: false, -// }, -// }, -// }, -// ], -// }; -// const transformedPayload: AzionConfig = {}; - -// strategy.transformToConfig(payload, transformedPayload); - -// expect(transformedPayload.applications).toEqual([ -// { -// name: 'test-application', -// active: true, -// debug: false, -// edgeCacheEnabled: true, -// functionsEnabled: false, -// applicationAcceleratorEnabled: true, -// imageProcessorEnabled: false, -// cache: undefined, -// rules: undefined, -// deviceGroups: undefined, -// functionsInstances: undefined, -// }, -// ]); -// }); - -// it('should transform a complete application manifest to config format', () => { -// // Mock return values for the strategy methods -// mockCacheStrategy.transformToConfig.mockReturnValue([{ name: 'test-cache', browser: { maxAgeSeconds: 3600 } }]); -// mockRulesStrategy.transformToConfig.mockReturnValue({ request: [], response: [] }); -// mockDeviceGroupsStrategy.transformToConfig.mockReturnValue([{ name: 'test-device-group', userAgent: 'test-agent' }]); -// mockFunctionInstancesStrategy.transformToConfig.mockReturnValue([{ name: 'test-function-instance', ref: 'test-function' }]); - -// const payload = { -// applications: [ -// { -// name: 'complete-application', -// active: false, -// debug: true, -// modules: { -// cache: { -// enabled: false, -// tiered_cache: { -// enabled: true, -// topology: 'near-edge' -// } -// }, -// functions: { -// enabled: true, -// }, -// application_accelerator: { -// enabled: false, -// }, -// image_processor: { -// enabled: true, -// }, -// }, -// cache_settings: [{ name: 'test-cache' }], -// rules: [{ name: 'test-rule' }], -// device_groups: [{ name: 'test-device-group' }], -// functions_instances: [{ name: 'test-function-instance' }], -// }, -// ], -// }; -// const transformedPayload: AzionConfig = {}; - -// strategy.transformToConfig(payload, transformedPayload); - -// expect(transformedPayload.applications).toEqual([ -// { -// name: 'complete-application', -// active: false, -// debug: true, -// edgeCacheEnabled: false, -// functionsEnabled: true, -// applicationAcceleratorEnabled: false, -// imageProcessorEnabled: true, -// cache: [{ name: 'test-cache', browser: { maxAgeSeconds: 3600 } }], -// rules: { request: [], response: [] }, -// deviceGroups: [{ name: 'test-device-group', userAgent: 'test-agent' }], -// functionsInstances: [{ name: 'test-function-instance', ref: 'test-function' }], -// }, -// ]); - -// // Verify that the strategy methods were called with the correct parameters -// expect(mockCacheStrategy.transformToConfig).toHaveBeenCalledWith([{ name: 'test-cache' }]); -// expect(mockRulesStrategy.transformToConfig).toHaveBeenCalledWith([{ name: 'test-rule' }]); -// expect(mockDeviceGroupsStrategy.transformToConfig).toHaveBeenCalledWith([{ name: 'test-device-group' }]); -// expect(mockFunctionInstancesStrategy.transformToConfig).toHaveBeenCalledWith([{ name: 'test-function-instance' }]); -// }); - -// it('should handle undefined optional properties', () => { -// const payload = { -// applications: [ -// { -// name: 'minimal-application', -// modules: {}, -// }, -// ], -// }; -// const transformedPayload: AzionConfig = {}; - -// strategy.transformToConfig(payload, transformedPayload); - -// expect(transformedPayload.applications).toEqual([ -// { -// name: 'minimal-application', -// active: undefined, -// debug: undefined, -// edgeCacheEnabled: undefined, -// functionsEnabled: undefined, -// applicationAcceleratorEnabled: undefined, -// imageProcessorEnabled: undefined, -// cache: undefined, -// rules: undefined, -// deviceGroups: undefined, -// functionsInstances: undefined, -// }, -// ]); -// }); - -// it('should transform multiple application manifests to config format', () => { -// const payload = { -// applications: [ -// { -// name: 'app-1', -// active: true, -// modules: { -// cache: { enabled: true }, -// }, -// }, -// { -// name: 'app-2', -// active: false, -// modules: { -// functions: { enabled: true }, -// }, -// }, -// ], -// }; -// const transformedPayload: AzionConfig = {}; - -// strategy.transformToConfig(payload, transformedPayload); - -// expect(transformedPayload.applications).toHaveLength(2); -// expect(transformedPayload.applications![0].name).toBe('app-1'); -// expect(transformedPayload.applications![1].name).toBe('app-2'); -// expect(transformedPayload.applications![0].active).toBe(true); -// expect(transformedPayload.applications![1].active).toBe(false); -// expect(transformedPayload.applications![0].edgeCacheEnabled).toBe(true); -// expect(transformedPayload.applications![1].functionsEnabled).toBe(true); -// }); -// }); -// }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/cacheProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/application/cacheProcessConfigStrategy.test.ts new file mode 100644 index 00000000..e0a424fa --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/application/cacheProcessConfigStrategy.test.ts @@ -0,0 +1,312 @@ +import { AzionCache } from '../../../../types'; +import CacheProcessConfigStrategy from './cacheProcessConfigStrategy'; + +describe('CacheProcessConfigStrategy', () => { + let strategy: CacheProcessConfigStrategy; + + beforeEach(() => { + strategy = new CacheProcessConfigStrategy(); + }); + + describe('transformToManifest', () => { + it('should return empty array when no cache settings are provided', () => { + const result = strategy.transformToManifest(undefined as unknown as AzionCache[]); + expect(result).toEqual([]); + }); + + it('should return empty array when cache settings array is empty', () => { + const result = strategy.transformToManifest([]); + expect(result).toEqual([]); + }); + + it('should transform a minimal cache setting using default values', () => { + const cache: AzionCache[] = [{ name: 'default-cache' }]; + + const result = strategy.transformToManifest(cache); + + expect(result).toEqual([ + { + name: 'default-cache', + browser_cache: { + behavior: 'honor', + max_age: 0, + }, + modules: { + cache: { + behavior: 'honor', + max_age: 60, + stale_cache: { enabled: false }, + large_file_cache: { enabled: false, offset: 1024 }, + tiered_cache: { enabled: false }, + }, + application_accelerator: { + cache_vary_by_method: [], + cache_vary_by_querystring: { + behavior: 'ignore', + fields: [], + sort_enabled: false, + }, + cache_vary_by_cookies: { + behavior: 'ignore', + cookie_names: [], + }, + cache_vary_by_devices: { + behavior: 'ignore', + device_group: [], + }, + }, + }, + }, + ]); + }); + + it('should override browser and edge max age when provided', () => { + const cache: AzionCache[] = [ + { + name: 'custom-cache', + browser: { maxAgeSeconds: 300 }, + edge: { maxAgeSeconds: 600 }, + }, + ]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].browser_cache).toEqual({ behavior: 'override', max_age: 300 }); + expect(result![0].modules.cache).toMatchObject({ behavior: 'override', max_age: 600 }); + }); + + it('should evaluate mathematical expressions for max age', () => { + const cache: AzionCache[] = [ + { + name: 'math-cache', + browser: { maxAgeSeconds: '60 * 60' }, + edge: { maxAgeSeconds: '60 * 60 * 24' }, + }, + ]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].browser_cache.max_age).toBe(3600); + expect(result![0].modules.cache.max_age).toBe(86400); + }); + + it('should throw an error when max age expression is not purely mathematical', () => { + const cache: AzionCache[] = [ + { + name: 'invalid-cache', + browser: { maxAgeSeconds: '60; DROP TABLE users' }, + }, + ]; + + expect(() => strategy.transformToManifest(cache)).toThrow( + 'Expression is not purely mathematical: 60; DROP TABLE users', + ); + }); + + it('should set cache_vary_by_method based on methods configuration', () => { + const cache: AzionCache[] = [ + { + name: 'methods-cache', + methods: { post: true, options: true }, + }, + ]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].modules.application_accelerator.cache_vary_by_method).toEqual(['post', 'options']); + }); + + it('should build cache_vary_by_querystring from cacheByQueryString', () => { + const cache: AzionCache[] = [ + { + name: 'querystring-cache', + queryStringSort: true, + cacheByQueryString: { option: 'allowlist', list: ['page', 'sort'] }, + }, + ]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].modules.application_accelerator.cache_vary_by_querystring).toEqual({ + behavior: 'allowlist', + fields: ['page', 'sort'], + sort_enabled: true, + }); + }); + + it('should build cache_vary_by_cookies from cacheByCookie', () => { + const cache: AzionCache[] = [ + { + name: 'cookie-cache', + cacheByCookie: { option: 'denylist', list: ['session_id'] }, + }, + ]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].modules.application_accelerator.cache_vary_by_cookies).toEqual({ + behavior: 'denylist', + cookie_names: ['session_id'], + }); + }); + + it('should enable stale cache when configured', () => { + const cache: AzionCache[] = [{ name: 'stale-cache', stale: true }]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].modules.cache.stale_cache.enabled).toBe(true); + }); + + it('should include tiered_cache topology only when tieredCache is enabled', () => { + const cache: AzionCache[] = [ + { + name: 'tiered-cache-enabled', + tieredCache: { enabled: true, topology: 'br-east-1' }, + }, + { + name: 'tiered-cache-disabled', + tieredCache: { enabled: false }, + }, + ]; + + const result = strategy.transformToManifest(cache); + + expect(result![0].modules.cache.tiered_cache).toEqual({ enabled: true, topology: 'br-east-1' }); + expect(result![1].modules.cache.tiered_cache).toEqual({ enabled: false }); + expect(result![1].modules.cache.tiered_cache.topology).toBeUndefined(); + }); + + it('should transform multiple cache settings', () => { + const cache: AzionCache[] = [{ name: 'cache-1' }, { name: 'cache-2' }]; + + const result = strategy.transformToManifest(cache); + + expect(result).toHaveLength(2); + expect(result![0].name).toBe('cache-1'); + expect(result![1].name).toBe('cache-2'); + }); + }); + + describe('transformToConfig', () => { + it('should return empty array when no cache settings are provided', () => { + const result = strategy.transformToConfig(undefined as unknown as unknown[]); + expect(result).toEqual([]); + }); + + it('should return empty array when cache settings array is empty', () => { + const result = strategy.transformToConfig([]); + expect(result).toEqual([]); + }); + + it('should transform a manifest cache setting to config format using default values', () => { + const payload = [ + { + name: 'default-cache', + browser_cache: { behavior: 'honor', max_age: 0 }, + modules: { + cache: { + behavior: 'honor', + max_age: 60, + stale_cache: { enabled: false }, + tiered_cache: { enabled: false }, + }, + application_accelerator: { + cache_vary_by_method: [], + cache_vary_by_querystring: { behavior: 'ignore', fields: [], sort_enabled: false }, + cache_vary_by_cookies: { behavior: 'ignore', cookie_names: [] }, + }, + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result).toEqual([ + { + name: 'default-cache', + stale: false, + browser: { maxAgeSeconds: 0 }, + edge: { maxAgeSeconds: 60 }, + methods: { post: false, options: false }, + queryStringSort: false, + tieredCache: { enabled: false }, + cacheByQueryString: { option: 'ignore', list: [] }, + cacheByCookie: { option: 'ignore', list: [] }, + }, + ]); + }); + + it('should transform cache_vary_by_method into methods', () => { + const payload = [ + { + name: 'methods-cache', + modules: { + cache: {}, + application_accelerator: { cache_vary_by_method: ['post', 'options'] }, + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result[0].methods).toEqual({ post: true, options: true }); + }); + + it('should include tieredCache topology only when tiered_cache is enabled', () => { + const payload = [ + { + name: 'tiered-cache-enabled', + modules: { + cache: { tiered_cache: { enabled: true, topology: 'us-east-1' } }, + application_accelerator: {}, + }, + }, + { + name: 'tiered-cache-disabled', + modules: { + cache: { tiered_cache: { enabled: false } }, + application_accelerator: {}, + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result[0].tieredCache).toEqual({ enabled: true, topology: 'us-east-1' }); + expect(result[1].tieredCache).toEqual({ enabled: false }); + expect(result[1].tieredCache!.topology).toBeUndefined(); + }); + + it('should evaluate mathematical expressions for max age', () => { + const payload = [ + { + name: 'math-cache', + browser_cache: { max_age: '60 * 60' }, + modules: { + cache: { max_age: '60 * 60 * 24' }, + application_accelerator: {}, + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result[0].browser!.maxAgeSeconds).toBe(3600); + expect(result[0].edge!.maxAgeSeconds).toBe(86400); + }); + + it('should transform multiple cache settings', () => { + const payload = [ + { name: 'cache-1', modules: { cache: {}, application_accelerator: {} } }, + { name: 'cache-2', modules: { cache: {}, application_accelerator: {} } }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe('cache-1'); + expect(result[1].name).toBe('cache-2'); + }); + }); +}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/deviceGroupsProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/application/deviceGroupsProcessConfigStrategy.test.ts new file mode 100644 index 00000000..d04e1602 --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/application/deviceGroupsProcessConfigStrategy.test.ts @@ -0,0 +1,104 @@ +import { AzionDeviceGroup } from '../../../../types'; +import DeviceGroupsProcessConfigStrategy from './deviceGroupsProcessConfigStrategy'; + +describe('DeviceGroupsProcessConfigStrategy', () => { + let strategy: DeviceGroupsProcessConfigStrategy; + + beforeEach(() => { + strategy = new DeviceGroupsProcessConfigStrategy(); + }); + + describe('transformToManifest', () => { + it('should return undefined when no device groups are provided', () => { + const result = strategy.transformToManifest(undefined as unknown as AzionDeviceGroup[]); + expect(result).toBeUndefined(); + }); + + it('should return undefined when device groups array is empty', () => { + const result = strategy.transformToManifest([]); + expect(result).toBeUndefined(); + }); + + it('should transform a single device group to manifest format', () => { + const deviceGroups: AzionDeviceGroup[] = [ + { + name: 'mobile', + userAgent: 'Mobile|Android|iPhone', + }, + ]; + + const result = strategy.transformToManifest(deviceGroups); + + expect(result).toEqual([ + { + name: 'mobile', + user_agent: 'Mobile|Android|iPhone', + }, + ]); + }); + + it('should transform multiple device groups to manifest format', () => { + const deviceGroups: AzionDeviceGroup[] = [ + { + name: 'mobile', + userAgent: 'Mobile|Android|iPhone', + }, + { + name: 'desktop', + userAgent: 'Windows|Macintosh', + }, + ]; + + const result = strategy.transformToManifest(deviceGroups); + + expect(result).toEqual([ + { name: 'mobile', user_agent: 'Mobile|Android|iPhone' }, + { name: 'desktop', user_agent: 'Windows|Macintosh' }, + ]); + }); + }); + + describe('transformToConfig', () => { + it('should return empty array when no payload is provided', () => { + const result = strategy.transformToConfig(undefined as unknown as Array<{ name: string; user_agent: string }>); + expect(result).toEqual([]); + }); + + it('should return empty array when payload is empty', () => { + const result = strategy.transformToConfig([]); + expect(result).toEqual([]); + }); + + it('should transform a single device group from manifest to config format', () => { + const payload = [ + { + name: 'mobile', + user_agent: 'Mobile|Android|iPhone', + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result).toEqual([ + { + name: 'mobile', + userAgent: 'Mobile|Android|iPhone', + }, + ]); + }); + + it('should transform multiple device groups from manifest to config format', () => { + const payload = [ + { name: 'mobile', user_agent: 'Mobile|Android|iPhone' }, + { name: 'desktop', user_agent: 'Windows|Macintosh' }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result).toEqual([ + { name: 'mobile', userAgent: 'Mobile|Android|iPhone' }, + { name: 'desktop', userAgent: 'Windows|Macintosh' }, + ]); + }); + }); +}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/functionInstancesProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/application/functionInstancesProcessConfigStrategy.test.ts new file mode 100644 index 00000000..9e8ab930 --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/application/functionInstancesProcessConfigStrategy.test.ts @@ -0,0 +1,205 @@ +import { AzionConfig, AzionFunctionInstance } from '../../../../types'; +import FunctionInstancesProcessConfigStrategy from './functionInstancesProcessConfigStrategy'; + +describe('FunctionInstancesProcessConfigStrategy', () => { + let strategy: FunctionInstancesProcessConfigStrategy; + + beforeEach(() => { + strategy = new FunctionInstancesProcessConfigStrategy(); + }); + + describe('transformToManifest', () => { + it('should return undefined when no function instances are provided', () => { + const config: AzionConfig = {}; + const result = strategy.transformToManifest(undefined as unknown as AzionFunctionInstance[], config); + expect(result).toBeUndefined(); + }); + + it('should return undefined when function instances array is empty', () => { + const config: AzionConfig = {}; + const result = strategy.transformToManifest([], config); + expect(result).toBeUndefined(); + }); + + it('should transform a single function instance to manifest format with default values', () => { + const config: AzionConfig = { + functions: [{ name: 'my-function', path: './functions/my-function.js' }], + }; + const functionInstances: AzionFunctionInstance[] = [ + { + name: 'my-function-instance', + ref: 'my-function', + }, + ]; + + const result = strategy.transformToManifest(functionInstances, config); + + expect(result).toEqual([ + { + name: 'my-function-instance', + function: 'my-function', + args: {}, + active: true, + }, + ]); + }); + + it('should transform a function instance with explicit args and active value', () => { + const config: AzionConfig = { + functions: [{ name: 'my-function', path: './functions/my-function.js' }], + }; + const functionInstances: AzionFunctionInstance[] = [ + { + name: 'my-function-instance', + ref: 'my-function', + args: { key: 'value' }, + active: false, + }, + ]; + + const result = strategy.transformToManifest(functionInstances, config); + + expect(result).toEqual([ + { + name: 'my-function-instance', + function: 'my-function', + args: { key: 'value' }, + active: false, + }, + ]); + }); + + it('should not validate function reference when ref is a number (ID)', () => { + const config: AzionConfig = {}; + const functionInstances: AzionFunctionInstance[] = [ + { + name: 'my-function-instance', + ref: 123, + }, + ]; + + expect(() => strategy.transformToManifest(functionInstances, config)).not.toThrow(); + + const result = strategy.transformToManifest(functionInstances, config); + expect(result![0].function).toBe(123); + }); + + it('should throw an error when function instance references a non-existent function', () => { + const config: AzionConfig = { + functions: [{ name: 'existing-function', path: './functions/existing-function.js' }], + }; + const functionInstances: AzionFunctionInstance[] = [ + { + name: 'my-function-instance', + ref: 'non-existent-function', + }, + ]; + + expect(() => strategy.transformToManifest(functionInstances, config)).toThrow( + 'Function instance "my-function-instance" references non-existent Function "non-existent-function".', + ); + }); + + it('should transform multiple function instances to manifest format', () => { + const config: AzionConfig = { + functions: [ + { name: 'function-1', path: './functions/function-1.js' }, + { name: 'function-2', path: './functions/function-2.js' }, + ], + }; + const functionInstances: AzionFunctionInstance[] = [ + { name: 'instance-1', ref: 'function-1' }, + { name: 'instance-2', ref: 'function-2', active: false }, + ]; + + const result = strategy.transformToManifest(functionInstances, config); + + expect(result).toHaveLength(2); + expect(result![0].name).toBe('instance-1'); + expect(result![0].active).toBe(true); + expect(result![1].name).toBe('instance-2'); + expect(result![1].active).toBe(false); + }); + }); + + describe('transformToConfig', () => { + it('should return empty array when no payload is provided', () => { + const result = strategy.transformToConfig( + undefined as unknown as Array<{ + name: string; + function: number | string; + args?: Record; + active?: boolean; + }>, + ); + expect(result).toEqual([]); + }); + + it('should return empty array when payload is empty', () => { + const result = strategy.transformToConfig([]); + expect(result).toEqual([]); + }); + + it('should transform a single function instance from manifest to config format', () => { + const payload = [ + { + name: 'my-function-instance', + function: 'my-function', + args: { key: 'value' }, + active: true, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result).toEqual([ + { + name: 'my-function-instance', + ref: 'my-function', + args: { key: 'value' }, + active: true, + }, + ]); + }); + + it('should default active to true when not provided', () => { + const payload = [ + { + name: 'my-function-instance', + function: 'my-function', + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result![0].active).toBe(true); + }); + + it('should transform a function instance referencing a function by ID', () => { + const payload = [ + { + name: 'my-function-instance', + function: 123, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result![0].ref).toBe(123); + }); + + it('should transform multiple function instances from manifest to config format', () => { + const payload = [ + { name: 'instance-1', function: 'function-1', active: true }, + { name: 'instance-2', function: 'function-2', active: false }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe('instance-1'); + expect(result[1].name).toBe('instance-2'); + expect(result[1].active).toBe(false); + }); + }); +}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/rulesProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/application/rulesProcessConfigStrategy.test.ts new file mode 100644 index 00000000..3e2949b7 --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/application/rulesProcessConfigStrategy.test.ts @@ -0,0 +1,478 @@ +import { AzionConnector, AzionFunction, AzionManifestRule, AzionRules } from '../../../../types'; +import RulesProcessConfigStrategy from './rulesProcessConfigStrategy'; + +describe('RulesProcessConfigStrategy', () => { + let strategy: RulesProcessConfigStrategy; + + beforeEach(() => { + strategy = new RulesProcessConfigStrategy(); + }); + + describe('transformToManifest', () => { + it('should return empty array when no rules are provided', () => { + const applicationRules: AzionRules = {}; + const result = strategy.transformToManifest(applicationRules); + expect(result).toEqual([]); + }); + + it('should return empty array when rules object has empty request and response arrays', () => { + const applicationRules: AzionRules = { request: [], response: [] }; + const result = strategy.transformToManifest(applicationRules); + expect(result).toEqual([]); + }); + + it('should transform request rules to manifest format', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'test-request-rule', + description: 'Test request rule', + active: true, + criteria: [ + [ + { + variable: 'request_uri', + conditional: 'if', + operator: 'is_equal', + argument: '/test', + }, + ], + ], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result).toEqual([ + { + phase: 'request', + rule: { + name: 'test-request-rule', + description: 'Test request rule', + active: true, + criteria: [ + [ + { + variable: '${request_uri}', + conditional: 'if', + operator: 'is_equal', + argument: '/test', + }, + ], + ], + behaviors: [{ type: 'deliver' }], + }, + }, + ]); + }); + + it('should transform response rules to manifest format', () => { + const applicationRules: AzionRules = { + response: [ + { + name: 'test-response-rule', + criteria: [ + [ + { + variable: '${host}', + conditional: 'if', + operator: 'is_equal', + argument: '404', + }, + ], + ], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result).toHaveLength(1); + expect(result[0].phase).toBe('response'); + expect(result[0].rule.name).toBe('test-response-rule'); + }); + + it('should default active to true when not provided', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'no-active-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result[0].rule.active).toBe(true); + }); + + it('should not add ${} wrapping when variable is already wrapped', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'wrapped-variable-rule', + criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result[0].rule.criteria[0][0].variable).toBe('${request_uri}'); + }); + + it('should not include argument for criteria operators that do not require a value', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'exists-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result[0].rule.criteria[0][0]).toEqual({ + variable: '${request_uri}', + conditional: 'if', + operator: 'exists', + }); + expect(result[0].rule.criteria[0][0]).not.toHaveProperty('argument'); + }); + + it('should include attributes for behaviors that require them', () => { + const functions: AzionFunction[] = [{ name: 'my-function', path: './functions/my-function.js' }]; + const applicationRules: AzionRules = { + request: [ + { + name: 'run-function-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'run_function', attributes: { value: 'my-function' } }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules, functions); + + expect(result[0].rule.behaviors).toEqual([{ type: 'run_function', attributes: { value: 'my-function' } }]); + }); + + it('should not include attributes key for behaviors without attributes', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'deliver-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result[0].rule.behaviors[0]).toEqual({ type: 'deliver' }); + expect(result[0].rule.behaviors[0]).not.toHaveProperty('attributes'); + }); + + it('should transform both request and response rules together', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'request-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + response: [ + { + name: 'response-rule', + criteria: [[{ variable: '${domain}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + ], + }; + + const result = strategy.transformToManifest(applicationRules); + + expect(result).toHaveLength(2); + expect(result[0].phase).toBe('request'); + expect(result[1].phase).toBe('response'); + }); + + it('should throw an error when a request rule references a non-existent function', () => { + const functions: AzionFunction[] = [{ name: 'existing-function', path: './functions/existing-function.js' }]; + const applicationRules: AzionRules = { + request: [ + { + name: 'invalid-function-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'run_function', attributes: { value: 'non-existent-function' } }], + }, + ], + }; + + expect(() => strategy.transformToManifest(applicationRules, functions)).toThrow( + 'Function "non-existent-function" referenced in rule "invalid-function-rule" is not defined in the functions array.', + ); + }); + + it('should throw an error when a response rule references a non-existent function', () => { + const functions: AzionFunction[] = [{ name: 'existing-function', path: './functions/existing-function.js' }]; + const applicationRules: AzionRules = { + response: [ + { + name: 'invalid-function-response-rule', + criteria: [[{ variable: '${domain}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'run_function', attributes: { value: 'non-existent-function' } }], + }, + ], + }; + + expect(() => strategy.transformToManifest(applicationRules, functions)).toThrow( + 'Function "non-existent-function" referenced in rule "invalid-function-response-rule" is not defined in the functions array.', + ); + }); + + it('should not validate function reference when value is a number (ID)', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'function-by-id-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'run_function', attributes: { value: 123 } }], + }, + ], + }; + + expect(() => strategy.transformToManifest(applicationRules, [])).not.toThrow(); + }); + + it('should throw an error when a rule references a non-existent connector', () => { + const connectors: AzionConnector[] = [ + { + name: 'existing-connector', + type: 'storage', + attributes: { bucket: 'my-bucket' }, + } as AzionConnector, + ]; + const applicationRules: AzionRules = { + request: [ + { + name: 'invalid-connector-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'set_connector', attributes: { value: 'non-existent-connector' } }], + }, + ], + }; + + expect(() => strategy.transformToManifest(applicationRules, undefined, connectors)).toThrow( + 'Connector "non-existent-connector" referenced in rule "invalid-connector-rule" is not defined in the connectors array.', + ); + }); + + it('should not throw when a rule references an existing connector', () => { + const connectors: AzionConnector[] = [ + { + name: 'existing-connector', + type: 'storage', + attributes: { bucket: 'my-bucket' }, + } as AzionConnector, + ]; + const applicationRules: AzionRules = { + request: [ + { + name: 'valid-connector-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'set_connector', attributes: { value: 'existing-connector' } }], + }, + ], + }; + + expect(() => strategy.transformToManifest(applicationRules, undefined, connectors)).not.toThrow(); + }); + + it('should not validate connector reference when value is a number (ID)', () => { + const applicationRules: AzionRules = { + request: [ + { + name: 'connector-by-id-rule', + criteria: [[{ variable: 'request_uri', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'set_connector', attributes: { value: 456 } }], + }, + ], + }; + + expect(() => strategy.transformToManifest(applicationRules, undefined, [])).not.toThrow(); + }); + }); + + describe('transformToConfig', () => { + it('should return empty request and response arrays when no payload is provided', () => { + const result = strategy.transformToConfig(undefined as unknown as AzionManifestRule[]); + expect(result).toEqual({ request: [], response: [] }); + }); + + it('should return empty request and response arrays when payload is empty', () => { + const result = strategy.transformToConfig([]); + expect(result).toEqual({ request: [], response: [] }); + }); + + it('should transform a request manifest rule to config format', () => { + const payload: AzionManifestRule[] = [ + { + phase: 'request', + rule: { + name: 'test-request-rule', + description: 'Test request rule', + active: true, + criteria: [ + [ + { + variable: '${request_uri}', + conditional: 'if', + operator: 'is_equal', + argument: '/test', + }, + ], + ], + behaviors: [{ type: 'deliver' }], + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result.request).toEqual([ + { + name: 'test-request-rule', + description: 'Test request rule', + active: true, + criteria: [ + [ + { + variable: '${request_uri}', + conditional: 'if', + operator: 'is_equal', + argument: '/test', + }, + ], + ], + behaviors: [{ type: 'deliver' }], + }, + ]); + expect(result.response).toEqual([]); + }); + + it('should transform a response manifest rule to config format', () => { + const payload: AzionManifestRule[] = [ + { + phase: 'response', + rule: { + name: 'test-response-rule', + active: true, + criteria: [[{ variable: '${uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result.request).toEqual([]); + expect(result.response).toHaveLength(1); + expect(result.response![0].name).toBe('test-response-rule'); + }); + + it('should default active to true when not provided', () => { + const payload: AzionManifestRule[] = [ + { + phase: 'request', + rule: { + name: 'no-active-rule', + criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + } as AzionManifestRule['rule'], + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result.request![0].active).toBe(true); + }); + + it('should not include argument for criteria without a value', () => { + const payload: AzionManifestRule[] = [ + { + phase: 'request', + rule: { + name: 'exists-rule', + criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result.request![0].criteria[0][0]).toEqual({ + variable: '${request_uri}', + conditional: 'if', + operator: 'exists', + }); + expect(result.request![0].criteria[0][0]).not.toHaveProperty('argument'); + }); + + it('should include attributes for behaviors that require them', () => { + const payload: AzionManifestRule[] = [ + { + phase: 'request', + rule: { + name: 'run-function-rule', + criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'run_function', attributes: { value: 'my-function' } }], + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result.request![0].behaviors).toEqual([{ type: 'run_function', attributes: { value: 'my-function' } }]); + }); + + it('should transform both request and response manifest rules together', () => { + const payload: AzionManifestRule[] = [ + { + phase: 'request', + rule: { + name: 'request-rule', + criteria: [[{ variable: '${request_uri}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + }, + { + phase: 'response', + rule: { + name: 'response-rule', + criteria: [[{ variable: '${args}', conditional: 'if', operator: 'exists' }]], + behaviors: [{ type: 'deliver' }], + }, + }, + ]; + + const result = strategy.transformToConfig(payload); + + expect(result.request).toHaveLength(1); + expect(result.response).toHaveLength(1); + expect(result.request![0].name).toBe('request-rule'); + expect(result.response![0].name).toBe('response-rule'); + }); + }); +}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/application/rulesProcessConfigStrategy_test_ts b/packages/config/src/configProcessor/processStrategy/implementations/application/rulesProcessConfigStrategy_test_ts deleted file mode 100644 index 95a5cf88..00000000 --- a/packages/config/src/configProcessor/processStrategy/implementations/application/rulesProcessConfigStrategy_test_ts +++ /dev/null @@ -1,863 +0,0 @@ -import { - AzionConnector, - AzionFunction, - AzionManifestRule, - AzionRules -} from '../../../../types'; -import RulesProcessConfigStrategy from './rulesProcessConfigStrategy'; - -describe('RulesProcessConfigStrategy', () => { - let strategy: RulesProcessConfigStrategy; - - beforeEach(() => { - strategy = new RulesProcessConfigStrategy(); - }); - - describe('transformToManifest', () => { - it('should return empty array when no rules are provided', () => { - const applicationRules: AzionRules = {}; - const result = strategy.transformToManifest(applicationRules); - expect(result).toEqual([]); - }); - - it('should return empty array when rules object is empty', () => { - const applicationRules: AzionRules = { request: [], response: [] }; - const result = strategy.transformToManifest(applicationRules); - expect(result).toEqual([]); - }); - - it('should transform request rules to manifest format', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-request-rule', - description: 'Test request rule', - active: true, - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - ], - }; - - const result = strategy.transformToManifest(applicationRules); - - expect(result).toEqual([ - { - phase: 'request', - rule: { - name: 'test-request-rule', - description: 'Test request rule', - active: true, - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - }, - ]); - }); - - it('should transform response rules to manifest format', () => { - const applicationRules: AzionRules = { - response: [ - { - name: 'test-response-rule', - description: 'Test response rule', - active: false, - criteria: [ - [ - { - variable: 'status', - conditional: 'if', - operator: 'is_equal', - argument: '404', - }, - ], - ], - behaviors: [ - { - type: 'add_response_header', - attributes: { - value: 'X-Custom-Header: Value', - }, - }, - ], - }, - ], - }; - - const result = strategy.transformToManifest(applicationRules); - - expect(result).toEqual([ - { - phase: 'response', - rule: { - name: 'test-response-rule', - description: 'Test response rule', - active: false, - criteria: [ - [ - { - variable: '${status}', - conditional: 'if', - operator: 'is_equal', - argument: '404', - }, - ], - ], - behaviors: [ - { - type: 'add_response_header', - attributes: { - value: 'X-Custom-Header: Value', - }, - }, - ], - }, - }, - ]); - }); - - it('should transform both request and response rules to manifest format', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-request-rule', - active: true, - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - ], - response: [ - { - name: 'test-response-rule', - active: false, - criteria: [ - [ - { - variable: 'status', - conditional: 'if', - operator: 'is_equal', - argument: '404', - }, - ], - ], - behaviors: [ - { - type: 'add_response_header', - attributes: { - value: 'X-Custom-Header: Value', - }, - }, - ], - }, - ], - }; - - const result = strategy.transformToManifest(applicationRules); - - expect(result).toHaveLength(2); - expect(result[0].phase).toBe('request'); - expect(result[1].phase).toBe('response'); - }); - - it('should handle variable with ${} prefix correctly', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - ], - }; - - const result = strategy.transformToManifest(applicationRules); - - expect(result[0].rule.criteria[0][0].variable).toBe('${request_uri}'); - }); - - it('should set default active status to true if not provided', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - ], - }; - - const result = strategy.transformToManifest(applicationRules); - - expect(result[0].rule.active).toBe(true); - }); - - it('should include behavior attributes when present', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'run_function', - attributes: { - value: 'test-function', - }, - }, - ], - }, - ], - }; - - const result = strategy.transformToManifest(applicationRules); - - expect(result[0].rule.behaviors[0]).toEqual({ - type: 'run_function', - attributes: { - value: 'test-function', - }, - }); - }); - - it('should validate function references and not throw error when function exists', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'run_function', - attributes: { - value: 'test-function', - }, - }, - ], - }, - ], - }; - - const functions: AzionFunction[] = [ - { - name: 'test-function', - path: './functions/test-function.js', - }, - ]; - - expect(() => strategy.transformToManifest(applicationRules, functions)).not.toThrow(); - }); - - it('should validate function references and throw error when function does not exist', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'run_function', - attributes: { - value: 'non-existent-function', - }, - }, - ], - }, - ], - }; - - const functions: AzionFunction[] = [ - { - name: 'test-function', - path: './functions/test-function.js', - }, - ]; - - expect(() => strategy.transformToManifest(applicationRules, functions)).toThrow( - 'Function "non-existent-function" referenced in rule "test-rule" is not defined in the functions array.' - ); - }); - - it('should not validate function references when function is a number (ID)', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'run_function', - attributes: { - value: 123, - }, - }, - ], - }, - ], - }; - - const functions: AzionFunction[] = [ - { - name: 'test-function', - path: './functions/test-function.js', - }, - ]; - - expect(() => strategy.transformToManifest(applicationRules, functions)).not.toThrow(); - }); - - it('should validate connector references and not throw error when connector exists', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'set_connector', - attributes: { - value: 'test-connector', - }, - }, - ], - }, - ], - }; - - const connectors: AzionConnector[] = [ - { - name: 'test-connector', - type: 'http', - attributes: { - addresses: [], - connectionOptions: {}, - }, - }, - ]; - - expect(() => strategy.transformToManifest(applicationRules, undefined, connectors)).not.toThrow(); - }); - - it('should validate connector references and throw error when connector does not exist', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'set_connector', - attributes: { - value: 'non-existent-connector', - }, - }, - ], - }, - ], - }; - - const connectors: AzionConnector[] = [ - { - name: 'test-connector', - type: 'http', - attributes: { - addresses: [], - connectionOptions: {}, - }, - }, - ]; - - expect(() => strategy.transformToManifest(applicationRules, undefined, connectors)).toThrow( - 'Connector "non-existent-connector" referenced in rule "test-rule" is not defined in the connectors array.' - ); - }); - - it('should not validate connector references when connector is a number (ID)', () => { - const applicationRules: AzionRules = { - request: [ - { - name: 'test-rule', - criteria: [ - [ - { - variable: 'request_uri', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'set_connector', - attributes: { - value: 123, - }, - }, - ], - }, - ], - }; - - const connectors: AzionConnector[] = [ - { - name: 'test-connector', - type: 'http', - attributes: { - addresses: [], - connectionOptions: {}, - }, - }, - ]; - - expect(() => strategy.transformToManifest(applicationRules, undefined, connectors)).not.toThrow(); - }); - }); - - describe('transformToConfig', () => { - it('should return empty rules object when no rules are provided', () => { - const rulesPayload: AzionManifestRule[] = []; - const result = strategy.transformToConfig(rulesPayload); - expect(result).toEqual({ request: [], response: [] }); - }); - - it('should handle non-array input', () => { - const rulesPayload = {} as any; - const result = strategy.transformToConfig(rulesPayload); - expect(result).toEqual({ request: [], response: [] }); - }); - - it('should transform request rules from manifest format to config format', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'request', - rule: { - name: 'test-request-rule', - description: 'Test request rule', - active: true, - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result).toEqual({ - request: [ - { - name: 'test-request-rule', - description: 'Test request rule', - active: true, - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - ], - response: [], - }); - }); - - it('should transform response rules from manifest format to config format', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'response', - rule: { - name: 'test-response-rule', - description: 'Test response rule', - active: false, - criteria: [ - [ - { - variable: '${status}', - conditional: 'if', - operator: 'is_equal', - argument: '404', - }, - ], - ], - behaviors: [ - { - type: 'add_response_header', - attributes: { - value: 'X-Custom-Header: Value', - }, - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result).toEqual({ - request: [], - response: [ - { - name: 'test-response-rule', - description: 'Test response rule', - active: false, - criteria: [ - [ - { - variable: '${status}', - conditional: 'if', - operator: 'is_equal', - argument: '404', - }, - ], - ], - behaviors: [ - { - type: 'add_response_header', - attributes: { - value: 'X-Custom-Header: Value', - }, - }, - ], - }, - ], - }); - }); - - it('should transform both request and response rules from manifest format to config format', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'request', - rule: { - name: 'test-request-rule', - active: true, - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - }, - { - phase: 'response', - rule: { - name: 'test-response-rule', - active: false, - criteria: [ - [ - { - variable: '${status}', - conditional: 'if', - operator: 'is_equal', - argument: '404', - }, - ], - ], - behaviors: [ - { - type: 'add_response_header', - attributes: { - value: 'X-Custom-Header: Value', - }, - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result.request).toHaveLength(1); - expect(result.response).toHaveLength(1); - expect(result.request![0].name).toBe('test-request-rule'); - expect(result.response![0].name).toBe('test-response-rule'); - }); - - it('should handle criteria with argument', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'request', - rule: { - name: 'test-rule', - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result.request![0].criteria[0][0]).toEqual({ - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }); - }); - - it('should handle criteria without argument', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'request', - rule: { - name: 'test-rule', - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'exists', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result.request![0].criteria[0][0]).toEqual({ - variable: '${request_uri}', - conditional: 'if', - operator: 'exists', - }); - }); - - it('should handle behavior with attributes', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'request', - rule: { - name: 'test-rule', - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'run_function', - attributes: { - value: 'test-function', - }, - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result.request![0].behaviors[0]).toEqual({ - type: 'run_function', - attributes: { - value: 'test-function', - }, - }); - }); - - it('should handle behavior without attributes', () => { - const rulesPayload: AzionManifestRule[] = [ - { - phase: 'request', - rule: { - name: 'test-rule', - criteria: [ - [ - { - variable: '${request_uri}', - conditional: 'if', - operator: 'is_equal', - argument: '/test', - }, - ], - ], - behaviors: [ - { - type: 'deliver', - }, - ], - }, - }, - ]; - - const result = strategy.transformToConfig(rulesPayload); - - expect(result.request![0].behaviors[0]).toEqual({ - type: 'deliver', - }); - }); - }); -}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.test.ts index b5314c9a..7283436f 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.test.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.test.ts @@ -340,6 +340,40 @@ describe('ConnectorProcessConfigStrategy', () => { expect(result![1].name).toBe('http-connector'); expect(result![1].type).toBe('http' as ConnectorType); }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + connectors: [ + { + name: 'connector-with-version', + type: 'storage' as ConnectorType, + versionId: 'version-abc-123', + attributes: { + bucket: 'my-bucket', + prefix: 'my-prefix', + }, + } as AzionConnector, + ], + }; + const configWithoutVersionId: AzionConfig = { + connectors: [ + { + name: 'connector-without-version', + type: 'storage' as ConnectorType, + attributes: { + bucket: 'my-bucket', + prefix: 'my-prefix', + }, + } as AzionConnector, + ], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId); + const resultWithout = strategy.transformToManifest(configWithoutVersionId); + + expect(resultWith![0].version_id).toBe('version-abc-123'); + expect(resultWithout![0].version_id).toBeUndefined(); + }); }); describe('transformToConfig', () => { @@ -688,5 +722,41 @@ describe('ConnectorProcessConfigStrategy', () => { expect(transformedPayload.connectors).toHaveLength(1); expect(transformedPayload.connectors![0].name).toBe('new-connector'); }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + connectors: [ + { + name: 'connector-with-version', + type: 'storage' as ConnectorType, + version_id: 'version-abc-123', + attributes: { + bucket: 'my-bucket', + prefix: 'my-prefix', + }, + }, + ], + }; + const payloadWithoutVersionId = { + connectors: [ + { + name: 'connector-without-version', + type: 'storage' as ConnectorType, + attributes: { + bucket: 'my-bucket', + prefix: 'my-prefix', + }, + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId); + strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId); + + expect(transformedWithVersionId.connectors![0].versionId).toBe('version-abc-123'); + expect(transformedWithoutVersionId.connectors![0].versionId).toBeUndefined(); + }); }); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.ts index 5fdac48b..c77cc37e 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/connectorProcessConfigStrategy.ts @@ -28,6 +28,7 @@ class ConnectorProcessConfigStrategy extends ProcessConfigStrategy { name: connector.name, active: connector.active ?? true, type: connector.type, + version_id: connector.versionId, }; // Handle different connector types @@ -116,6 +117,7 @@ class ConnectorProcessConfigStrategy extends ProcessConfigStrategy { name: string; active?: boolean; type: ConnectorType; + version_id?: string; attributes: { // For storage bucket?: string; @@ -183,6 +185,7 @@ class ConnectorProcessConfigStrategy extends ProcessConfigStrategy { name: connector.name, active: connector.active, type: connector.type, + versionId: connector.version_id, }; // Handle different connector types diff --git a/packages/config/src/configProcessor/processStrategy/implementations/customPagesProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/customPagesProcessConfigStrategy.ts index 27a02269..a3e5014d 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/customPagesProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/customPagesProcessConfigStrategy.ts @@ -61,6 +61,7 @@ class CustomPagesProcessConfigStrategy extends ProcessConfigStrategy { }, }, })), + version_id: customPage.versionId, }; }); } @@ -85,6 +86,7 @@ class CustomPagesProcessConfigStrategy extends ProcessConfigStrategy { }; }; }>; + version_id?: string; }>; }, transformedPayload: AzionConfig, @@ -108,6 +110,7 @@ class CustomPagesProcessConfigStrategy extends ProcessConfigStrategy { }, }, })), + versionId: customPage.version_id, })); return transformedPayload.customPages; diff --git a/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.test.ts index adc81414..7ec48964 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.test.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.test.ts @@ -203,6 +203,32 @@ describe('FunctionsProcessConfigStrategy', () => { expect(() => strategy.transformToManifest(config)).not.toThrow(); }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + functions: [ + { + name: 'function-with-version', + path: './functions/function-with-version.js', + versionId: 'version-abc-123', + }, + ], + }; + const configWithoutVersionId: AzionConfig = { + functions: [ + { + name: 'function-without-version', + path: './functions/function-without-version.js', + }, + ], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId); + const resultWithout = strategy.transformToManifest(configWithoutVersionId); + + expect(resultWith[0].version_id).toBe('version-abc-123'); + expect(resultWithout[0].version_id).toBeUndefined(); + }); }); describe('transformToConfig', () => { @@ -334,5 +360,39 @@ describe('FunctionsProcessConfigStrategy', () => { expect(transformedPayload.functions).toHaveLength(1); expect(transformedPayload.functions![0].name).toBe('new-function'); }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + functions: [ + { + name: 'function-with-version', + runtime: 'azion_js', + default_args: {}, + execution_environment: 'application', + active: true, + version_id: 'version-abc-123', + }, + ], + }; + const payloadWithoutVersionId = { + functions: [ + { + name: 'function-without-version', + runtime: 'azion_js', + default_args: {}, + execution_environment: 'application', + active: true, + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId); + strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId); + + expect(transformedWithVersionId.functions![0].versionId).toBe('version-abc-123'); + expect(transformedWithoutVersionId.functions![0].versionId).toBeUndefined(); + }); }); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts index 4fadb246..bfc9a5f7 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts @@ -41,6 +41,7 @@ class FunctionsProcessConfigStrategy extends ProcessConfigStrategy { active: func.active ?? true, path: func.path, bindings: func.bindings, + version_id: func.versionId, }; }); } @@ -56,6 +57,7 @@ class FunctionsProcessConfigStrategy extends ProcessConfigStrategy { default_args?: Record; execution_environment?: string; active?: boolean; + version_id?: string; }>; }, transformedPayload: AzionConfig, @@ -71,6 +73,7 @@ class FunctionsProcessConfigStrategy extends ProcessConfigStrategy { defaultArgs: func.default_args, executionEnvironment: func.execution_environment as FunctionExecutionEnvironment, active: func.active, + versionId: func.version_id, })); } } diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts index 6f52e35e..4746e8de 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts @@ -309,6 +309,36 @@ describe('FirewallProcessConfigStrategy', () => { ]); expect(rule.behaviors).toEqual([{ type: 'deny' }]); }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + firewall: [ + { + name: 'fw-with-version', + functions: false, + networkProtection: true, + waf: false, + versionId: 'version-abc-123', + }, + ], + }; + const configWithoutVersionId: AzionConfig = { + firewall: [ + { + name: 'fw-without-version', + functions: false, + networkProtection: true, + waf: false, + }, + ], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId)!; + const resultWithout = strategy.transformToManifest(configWithoutVersionId)!; + + expect(resultWith[0].version_id).toBe('version-abc-123'); + expect(resultWithout[0].version_id).toBeUndefined(); + }); }); describe('transformToConfig', () => { @@ -588,5 +618,33 @@ describe('FirewallProcessConfigStrategy', () => { ], }); }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + firewall: [ + { + name: 'fw-with-version', + modules: { functions: { enabled: false }, network_protection: { enabled: true }, waf: { enabled: false } }, + version_id: 'version-abc-123', + }, + ], + }; + const payloadWithoutVersionId = { + firewall: [ + { + name: 'fw-without-version', + modules: { functions: { enabled: false }, network_protection: { enabled: true }, waf: { enabled: false } }, + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId); + strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId); + + expect(transformedWithVersionId.firewall?.[0]?.versionId).toBe('version-abc-123'); + expect(transformedWithoutVersionId.firewall?.[0]?.versionId).toBeUndefined(); + }); }); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts index 2cb79d67..b18b9fcf 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts @@ -96,6 +96,10 @@ class FirewallProcessConfigStrategy extends ProcessConfigStrategy { }); } + if (fw.versionId) { + payload.version_id = fw.versionId; + } + return payload; }); } @@ -221,6 +225,10 @@ class FirewallProcessConfigStrategy extends ProcessConfigStrategy { })); } + if (fw.version_id) { + firewallConfig.versionId = fw.version_id; + } + return firewallConfig; }); return transformedPayload.firewall; diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.test.ts index 45af170b..57683519 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.test.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.test.ts @@ -110,6 +110,34 @@ describe('NetworkListProcessConfigStrategy', () => { expect(result![2].items).toEqual(['BR', 'US', 'CA']); expect(result![2].active).toBe(true); }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + networkList: [ + { + name: 'network-list-with-version', + type: 'ip_cidr' as NetworkListType, + items: ['192.168.1.1/24'], + versionId: 'version-abc-123', + }, + ], + }; + const configWithoutVersionId: AzionConfig = { + networkList: [ + { + name: 'network-list-without-version', + type: 'ip_cidr' as NetworkListType, + items: ['192.168.1.1/24'], + }, + ], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId); + const resultWithout = strategy.transformToManifest(configWithoutVersionId); + + expect(resultWith![0].version_id).toBe('version-abc-123'); + expect(resultWithout![0].version_id).toBeUndefined(); + }); }); describe('transformToConfig', () => { @@ -233,5 +261,37 @@ describe('NetworkListProcessConfigStrategy', () => { expect(transformedPayload.networkList![0].type).toBe('ip_cidr'); expect(transformedPayload.networkList![0].items).toEqual(['192.168.1.1/24']); }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + networkList: [ + { + name: 'network-list-with-version', + type: 'ip_cidr', + items: ['192.168.1.1/24'], + active: true, + version_id: 'version-abc-123', + }, + ], + }; + const payloadWithoutVersionId = { + networkList: [ + { + name: 'network-list-without-version', + type: 'ip_cidr', + items: ['192.168.1.1/24'], + active: true, + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId); + strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId); + + expect(transformedWithVersionId.networkList![0].versionId).toBe('version-abc-123'); + expect(transformedWithoutVersionId.networkList![0].versionId).toBeUndefined(); + }); }); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.ts index 439d8d47..b18dd4a5 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/networkListProcessConfigStrategy.ts @@ -24,6 +24,7 @@ class NetworkListProcessConfigStrategy extends ProcessConfigStrategy { type: network.type, items: network.items, active: network.active ?? true, + version_id: network.versionId, }; payload.push(item); }); @@ -48,6 +49,7 @@ class NetworkListProcessConfigStrategy extends ProcessConfigStrategy { type: network.type, items: network.items, active: network.active, + versionId: network.version_id, }; transformedPayload.networkList!.push(item); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.test.ts index b4867ce0..e3e8bf1b 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.test.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.test.ts @@ -149,6 +149,46 @@ describe('WafProcessConfigStrategy', () => { expect(result![1].engine_settings.attributes.thresholds[0].threat).toBe('cross_site_scripting'); expect(result![1].engine_settings.attributes.thresholds[1].sensitivity).toBe('highest'); }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + waf: [ + { + name: 'waf-with-version', + versionId: 'version-abc-123', + engineSettings: { + engineVersion: '2021-Q3' as WafEngineVersion, + type: 'score' as WafEngineType, + attributes: { + rulesets: [1] as WafRuleset[], + thresholds: [], + }, + }, + }, + ], + }; + const configWithoutVersionId: AzionConfig = { + waf: [ + { + name: 'waf-without-version', + engineSettings: { + engineVersion: '2021-Q3' as WafEngineVersion, + type: 'score' as WafEngineType, + attributes: { + rulesets: [1] as WafRuleset[], + thresholds: [], + }, + }, + }, + ], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId); + const resultWithout = strategy.transformToManifest(configWithoutVersionId); + + expect(resultWith![0].version_id).toBe('version-abc-123'); + expect(resultWithout![0].version_id).toBeUndefined(); + }); }); describe('transformToConfig', () => { @@ -317,5 +357,49 @@ describe('WafProcessConfigStrategy', () => { expect(transformedPayload.waf).toHaveLength(1); expect(transformedPayload.waf![0].name).toBe('new-waf'); }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + waf: [ + { + name: 'waf-with-version', + product_version: '1.0', + version_id: 'version-abc-123', + engine_settings: { + engine_version: '2021-Q3', + type: 'score', + attributes: { + rulesets: [1], + thresholds: [], + }, + }, + }, + ], + }; + const payloadWithoutVersionId = { + waf: [ + { + name: 'waf-without-version', + product_version: '1.0', + engine_settings: { + engine_version: '2021-Q3', + type: 'score', + attributes: { + rulesets: [1], + thresholds: [], + }, + }, + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId); + strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId); + + expect(transformedWithVersionId.waf![0].versionId).toBe('version-abc-123'); + expect(transformedWithoutVersionId.waf![0].versionId).toBeUndefined(); + }); }); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.ts index da19321c..60b4778e 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/wafProcessConfigStrategy.ts @@ -1,4 +1,4 @@ -import { AzionConfig, WafThreshold } from '../../../../types'; +import { AzionConfig, AzionWaf, WafThreshold } from '../../../../types'; import ProcessConfigStrategy from '../../processConfigStrategy'; /** @@ -34,6 +34,12 @@ class WafProcessConfigStrategy extends ProcessConfigStrategy { }, }, }; + + if (wafConfig.versionId) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (item as any).version_id = wafConfig.versionId; + } + payload.push(item); }); @@ -52,7 +58,7 @@ class WafProcessConfigStrategy extends ProcessConfigStrategy { transformedPayload.waf = []; waf.forEach((wafItem) => { - const item = { + const item: AzionWaf = { name: wafItem.name, productVersion: wafItem.product_version, engineSettings: { @@ -68,6 +74,10 @@ class WafProcessConfigStrategy extends ProcessConfigStrategy { }, }; + if (wafItem.version_id) { + item.versionId = wafItem.version_id; + } + transformedPayload.waf!.push(item); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.test.ts index 2270e4db..58793766 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.test.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.test.ts @@ -146,6 +146,32 @@ describe('WorkloadProcessConfigStrategy', () => { expect(result[0].name).toBe('workload-1'); expect(result[1].name).toBe('workload-2'); }); + + it('should include version_id in the manifest only when versionId is provided (optional field)', () => { + const configWithVersionId: AzionConfig = { + workloads: [ + { + name: 'workload-with-version', + domains: ['example.com'], + versionId: 'version-abc-123', + }, + ], + }; + const configWithoutVersionId: AzionConfig = { + workloads: [ + { + name: 'workload-without-version', + domains: ['example.com'], + }, + ], + }; + + const resultWith = strategy.transformToManifest(configWithVersionId, {}); + const resultWithout = strategy.transformToManifest(configWithoutVersionId, {}); + + expect(resultWith[0].version_id).toBe('version-abc-123'); + expect(resultWithout[0].version_id).toBeUndefined(); + }); }); describe('transformToConfig', () => { @@ -388,5 +414,37 @@ describe('WorkloadProcessConfigStrategy', () => { expect(result[0].mtls).toBeUndefined(); }); + + it('should include versionId in the config only when version_id is provided (optional field)', () => { + const payloadWithVersionId = { + workloads: [ + { + name: 'workload-with-version', + domains: ['example.com'], + version_id: 'version-abc-123', + }, + ], + }; + const payloadWithoutVersionId = { + workloads: [ + { + name: 'workload-without-version', + domains: ['example.com'], + }, + ], + }; + + const transformedWithVersionId: AzionConfig = {}; + const transformedWithoutVersionId: AzionConfig = {}; + const resultWith = strategy.transformToConfig(payloadWithVersionId, transformedWithVersionId) as Array<{ + versionId?: string; + }>; + const resultWithout = strategy.transformToConfig(payloadWithoutVersionId, transformedWithoutVersionId) as Array<{ + versionId?: string; + }>; + + expect(resultWith[0].versionId).toBe('version-abc-123'); + expect(resultWithout[0].versionId).toBeUndefined(); + }); }); }); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.ts index 96eb442c..33df5119 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/workloadProcessConfigStrategy.ts @@ -41,6 +41,7 @@ class WorkloadProcessConfigStrategy extends ProcessConfigStrategy { }, } : undefined, + version_id: workload.versionId, })); } @@ -79,6 +80,7 @@ class WorkloadProcessConfigStrategy extends ProcessConfigStrategy { }, } : undefined, + versionId: workload.version_id, })); return transformedPayload.workloads; diff --git a/packages/config/src/configProcessor/schemas/applications/index.ts b/packages/config/src/configProcessor/schemas/applications/index.ts index c3499265..69e47546 100644 --- a/packages/config/src/configProcessor/schemas/applications/index.ts +++ b/packages/config/src/configProcessor/schemas/applications/index.ts @@ -451,6 +451,13 @@ const applicationsSchema = { }, errorMessage: "The 'functions' field must be an array of function instance objects", }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name'], additionalProperties: false, diff --git a/packages/config/src/configProcessor/schemas/connectors/index.ts b/packages/config/src/configProcessor/schemas/connectors/index.ts index 0e1cb116..325bb359 100644 --- a/packages/config/src/configProcessor/schemas/connectors/index.ts +++ b/packages/config/src/configProcessor/schemas/connectors/index.ts @@ -312,6 +312,13 @@ const connectorsSchema = { errorMessage: "The 'attributes' field must match either storage format (bucket, prefix) or HTTP/Live Ingest format (addresses, connectionOptions, modules).", }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name', 'type', 'attributes'], additionalProperties: false, diff --git a/packages/config/src/configProcessor/schemas/customPages/index.ts b/packages/config/src/configProcessor/schemas/customPages/index.ts index 67228093..e564052e 100644 --- a/packages/config/src/configProcessor/schemas/customPages/index.ts +++ b/packages/config/src/configProcessor/schemas/customPages/index.ts @@ -91,6 +91,13 @@ const customPagesSchema = { }, errorMessage: "The 'pages' field must be an array of page configurations with at least one item", }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name', 'pages'], additionalProperties: false, diff --git a/packages/config/src/configProcessor/schemas/firewall/index.ts b/packages/config/src/configProcessor/schemas/firewall/index.ts index 0375741e..313c99cb 100644 --- a/packages/config/src/configProcessor/schemas/firewall/index.ts +++ b/packages/config/src/configProcessor/schemas/firewall/index.ts @@ -149,6 +149,13 @@ const firewall = { type: 'array', items: firewallFunctionsInstances, }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name'], additionalProperties: false, diff --git a/packages/config/src/configProcessor/schemas/functions/index.ts b/packages/config/src/configProcessor/schemas/functions/index.ts index 4146bec2..b79b696c 100644 --- a/packages/config/src/configProcessor/schemas/functions/index.ts +++ b/packages/config/src/configProcessor/schemas/functions/index.ts @@ -66,6 +66,13 @@ const functionsSchema = { additionalProperties: 'No additional properties are allowed in the bindings object', }, }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name', 'path'], additionalProperties: false, diff --git a/packages/config/src/configProcessor/schemas/networkList/index.ts b/packages/config/src/configProcessor/schemas/networkList/index.ts index 48b36707..057da100 100644 --- a/packages/config/src/configProcessor/schemas/networkList/index.ts +++ b/packages/config/src/configProcessor/schemas/networkList/index.ts @@ -33,6 +33,13 @@ const networkListSchema = { default: true, errorMessage: "The 'active' field must be a boolean.", }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name', 'type', 'items'], additionalProperties: false, diff --git a/packages/config/src/configProcessor/schemas/workloads/index.ts b/packages/config/src/configProcessor/schemas/workloads/index.ts index 4a9539fc..1884d292 100644 --- a/packages/config/src/configProcessor/schemas/workloads/index.ts +++ b/packages/config/src/configProcessor/schemas/workloads/index.ts @@ -212,6 +212,13 @@ const workloadsSchema = { minItems: 1, errorMessage: "The 'deployments' field must be an array of deployment objects with at least one item.", }, + versionId: { + type: 'string', + minLength: 1, + maxLength: 36, + pattern: '.*', + errorMessage: "The 'versionId' field must be a string between 1 and 36 characters", + }, }, required: ['name', 'deployments'], additionalProperties: false, diff --git a/packages/config/src/rules/request/createMPA.ts b/packages/config/src/rules/request/createMPA.ts index 286858ca..3c440aab 100644 --- a/packages/config/src/rules/request/createMPA.ts +++ b/packages/config/src/rules/request/createMPA.ts @@ -1,4 +1,4 @@ -import type { AzionRules } from '@aziontech/config'; +import type { AzionRules } from '../../types'; import { ALL_EXTENSIONS } from '../constants'; /** diff --git a/packages/config/src/rules/request/createSPA.ts b/packages/config/src/rules/request/createSPA.ts index e98679a6..5601f22e 100644 --- a/packages/config/src/rules/request/createSPA.ts +++ b/packages/config/src/rules/request/createSPA.ts @@ -1,4 +1,4 @@ -import type { AzionRules } from '@aziontech/config'; +import type { AzionRules } from '../../types'; import { ALL_EXTENSIONS } from '../constants'; /** diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 8e343e4a..b6d298b2 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -469,6 +469,8 @@ export type AzionNetworkList = { items: string[]; /** Active status */ active?: boolean; + /** Version ID */ + versionId?: string; }; /** @@ -521,6 +523,8 @@ export type AzionFunction = { active?: boolean; /** Function bindings */ bindings?: AzionFunctionBindings; + /** Version ID */ + versionId?: string; }; /** @@ -563,6 +567,8 @@ export type AzionApplication = { deviceGroups?: AzionDeviceGroup[]; /** Function instances */ functionsInstances?: AzionFunctionInstance[]; + /** Version ID */ + versionId?: string; }; /** @@ -665,6 +671,8 @@ export type AzionFirewall = { debugRules?: boolean; /** Functions Instances */ functionsInstances?: AzionFirewallFunctionsInstance[]; + /** Version ID */ + versionId?: string; }; // WAF V4 Types @@ -698,6 +706,8 @@ export type AzionWaf = { productVersion?: string; /** Engine settings */ engineSettings: WafEngineSettings; + /** Version ID */ + versionId?: string; }; export type BuildConfiguration = Omit, 'preset' | 'entry'> & { @@ -828,6 +838,8 @@ export type AzionWorkload = { workloadDomainAllowAccess?: boolean; /** Workload deployments */ deployments?: AzionWorkloadDeployment[]; + /** Version ID */ + versionId?: string; }; // Connector V4 Types @@ -959,6 +971,8 @@ export interface AzionConnectorStorage { type: 'storage'; /** Connector attributes */ attributes: ConnectorStorageAttributes; + /** Version ID */ + versionId?: string; } export interface AzionConnectorHttp { @@ -970,6 +984,8 @@ export interface AzionConnectorHttp { type: 'http'; /** Connector attributes */ attributes: ConnectorHttpAttributes; + /** Version ID */ + versionId?: string; } export interface AzionConnectorLiveIngest { @@ -981,6 +997,8 @@ export interface AzionConnectorLiveIngest { type: 'live_ingest'; /** Connector attributes */ attributes: ConnectorLiveIngestAttributes; + /** Version ID */ + versionId?: string; } export type AzionConnector = AzionConnectorStorage | AzionConnectorHttp | AzionConnectorLiveIngest; @@ -1019,6 +1037,8 @@ export interface AzionCustomPage { active?: boolean; /** Array of error page configurations */ pages: AzionCustomPageEntry[]; + /** Version ID */ + versionId?: string; } /** diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index e80eca4a..3073404b 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -1,31 +1,31 @@ import { - deleteBucket, - deleteObject, - getBucketByName, - getBuckets, - getObjectByKey, - getObjects, - patchBucket, - postBucket, - postObject, - putObject, + deleteBucket, + deleteObject, + getBucketByName, + getBuckets, + getObjectByKey, + getObjects, + patchBucket, + postBucket, + postObject, + putObject, } from './services/api/index'; import { - AzionBucket, - AzionBucketCollection, - AzionBucketCollectionParams, - AzionBucketObject, - AzionBucketObjects, - AzionClientOptions, - AzionDeletedBucket, - AzionDeletedBucketObject, - AzionEnvironment, - AzionObjectCollectionParams, - AzionStorageClient, - AzionStorageResponse, - ContentObjectStorage, - CreateAzionStorageClient, - EdgeAccessType, + AzionBucket, + AzionBucketCollection, + AzionBucketCollectionParams, + AzionBucketObject, + AzionBucketObjects, + AzionClientOptions, + AzionDeletedBucket, + AzionDeletedBucketObject, + AzionEnvironment, + AzionObjectCollectionParams, + AzionStorageClient, + AzionStorageResponse, + ContentObjectStorage, + CreateAzionStorageClient, + EdgeAccessType, } from './types'; import { InternalStorageClient, isInternalStorageAvailable } from './services/runtime/index'; @@ -1122,18 +1122,18 @@ const client: CreateAzionStorageClient = ( }; export { - createBucketWrapper as createBucket, - client as createClient, - createObjectWrapper as createObject, - deleteBucketWrapper as deleteBucket, - deleteObjectWrapper as deleteObject, - getBucketWrapper as getBucket, - getBucketsWrapper as getBuckets, - getObjectByKeyWrapper as getObjectByKey, - getObjectsWrapper as getObjects, - setupStorageWrapper as setupStorage, - updateBucketWrapper as updateBucket, - updateObjectWrapper as updateObject + createBucketWrapper as createBucket, + client as createClient, + createObjectWrapper as createObject, + deleteBucketWrapper as deleteBucket, + deleteObjectWrapper as deleteObject, + getBucketWrapper as getBucket, + getBucketsWrapper as getBuckets, + getObjectByKeyWrapper as getObjectByKey, + getObjectsWrapper as getObjects, + setupStorageWrapper as setupStorage, + updateBucketWrapper as updateBucket, + updateObjectWrapper as updateObject, }; export default client; diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index 158314f6..79a64af8 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -1,17 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { fetchWithErrorHandling } from '../../utils/index'; import { - ApiCreateBucketResponse, - ApiCreateObjectResponse, - ApiDeleteBucketResponse, - ApiDeleteObjectResponse, - ApiEditBucketResponse, - ApiError, - ApiGetBucketResponse, - ApiListBucketsParams, - ApiListBucketsResponse, - ApiListObjectsParams, - ApiListObjectsResponse, + ApiCreateBucketResponse, + ApiCreateObjectResponse, + ApiDeleteBucketResponse, + ApiDeleteObjectResponse, + ApiEditBucketResponse, + ApiError, + ApiGetBucketResponse, + ApiListBucketsParams, + ApiListBucketsResponse, + ApiListObjectsParams, + ApiListObjectsResponse, } from './types'; import { AzionEnvironment, ContentObjectStorage, EdgeAccessType } from '../../types'; @@ -554,15 +554,14 @@ const deleteObject = async ( }; export { - deleteBucket, - deleteObject, - getBucketByName, - getBuckets, - getObjectByKey, - getObjects, - patchBucket, - postBucket, - postObject, - putObject + deleteBucket, + deleteObject, + getBucketByName, + getBuckets, + getObjectByKey, + getObjects, + patchBucket, + postBucket, + postObject, + putObject, }; - diff --git a/packages/storage/src/types.ts b/packages/storage/src/types.ts index 51cc0f9c..ce72b6e4 100644 --- a/packages/storage/src/types.ts +++ b/packages/storage/src/types.ts @@ -164,7 +164,10 @@ export interface AzionStorageClient { * @param {EdgeAccessType} params.workloads_access - Workloads access configuration for the bucket. * @returns {Promise} The created bucket or error message. */ - createBucket: (params: { name: string; workloads_access: EdgeAccessType }) => Promise>; + createBucket: (params: { + name: string; + workloads_access: EdgeAccessType; + }) => Promise>; /** * Updates a bucket by its name. @@ -173,7 +176,10 @@ export interface AzionStorageClient { * @param {EdgeAccessType} params.workloads_access - New Workloads access configuration for the bucket. * @returns {Promise>} The updated bucket or error message. */ - updateBucket: (params: { name: string; workloads_access: EdgeAccessType }) => Promise>; + updateBucket: (params: { + name: string; + workloads_access: EdgeAccessType; + }) => Promise>; /** * Deletes a bucket by its name. * @param {Object} params - Parameters for deleting a bucket. @@ -195,7 +201,10 @@ export interface AzionStorageClient { * @param {EdgeAccessType} params.workloads_access - Workloads access configuration for the bucket (used only if creating). * @returns {Promise>} The existing or created bucket. */ - setupStorage: (params: { name: string; workloads_access: EdgeAccessType }) => Promise>; + setupStorage: (params: { + name: string; + workloads_access: EdgeAccessType; + }) => Promise>; } export type AzionBucketCollectionParams = {