diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml index 3af0081df4fcd..e6f2cc8091dd4 100644 --- a/.buildkite/ftr_security_stateful_configs.yml +++ b/.buildkite/ftr_security_stateful_configs.yml @@ -135,6 +135,7 @@ enabled: - x-pack/platform/test/spaces_api_integration/deployment_agnostic/security_and_spaces/stateful.config_trial.ts - x-pack/platform/test/spaces_api_integration/deployment_agnostic/security_and_spaces/stateful.copy_to_space.config_trial.ts - x-pack/platform/test/spaces_api_integration/deployment_agnostic/security_and_spaces/stateful.copy_to_space.config_basic.ts + - x-pack/platform/test/automatic_import_v2_api_integration/configs/config.stateful.ts - x-pack/solutions/security/test/alerting_api_integration/security_and_spaces/group2/config.ts - x-pack/solutions/security/test/alerting_api_integration/security_and_spaces/group2/config_non_dedicated_task_runner.ts - x-pack/solutions/security/test/cases_api_integration/security_and_spaces/config_trial.ts diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/common/index.ts b/x-pack/platform/plugins/shared/automatic_import_v2/common/index.ts index 73bce9317db68..0f308767b5d56 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/common/index.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/common/index.ts @@ -6,16 +6,19 @@ */ export type { - CreateAutoImportIntegrationRequestBody, CreateAutoImportIntegrationResponse, DeleteAutoImportIntegrationRequestParams, - GetAutoImportIntegrationRequestParams, GetAutoImportIntegrationResponse, GetAutoImportIntegrationsResponse, UpdateAutoImportIntegrationRequestBody, UpdateAutoImportIntegrationRequestParams, } from './model/api/integrations/integration.gen'; +export { + CreateAutoImportIntegrationRequestBody, + GetAutoImportIntegrationRequestParams, +} from './model/api/integrations/integration.gen'; + export type { DataStreamSamples } from './model/index_samples.gen'; -export type { Integration } from './model/common_attributes.gen'; +export type { Integration, DataStream } from './model/common_attributes.gen'; diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.gen.ts b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.gen.ts index 092110a3d138f..818357e15eca6 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.gen.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.gen.ts @@ -62,7 +62,7 @@ export const CreateAutoImportIntegrationRequestBody = z }) .strict() ) - .min(1), + .optional(), }) .strict(); export type CreateAutoImportIntegrationRequestBodyInput = z.input< diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.schema.yaml b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.schema.yaml index 41749696c5316..7653e53228e7f 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.schema.yaml +++ b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/api/integrations/integration.schema.yaml @@ -23,7 +23,6 @@ paths: required: - title - description - - dataStreams properties: title: description: The title of the integration @@ -38,7 +37,6 @@ paths: dataStreams: description: The data streams of the integration type: array - minItems: 1 items: type: object additionalProperties: false diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.gen.ts b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.gen.ts index 92d7608d5960f..65bf8934dced5 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.gen.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.gen.ts @@ -48,6 +48,10 @@ export const InputType = z.object({ */ export type DataStream = z.infer; export const DataStream = z.object({ + /** + * The data stream id + */ + data_stream_id: NonEmptyString.optional(), /** * The title of the data stream */ @@ -56,6 +60,10 @@ export const DataStream = z.object({ * The description of the data stream */ description: NonEmptyString.optional(), + /** + * The status of the data stream + */ + status: NonEmptyString.optional(), /** * The input types of the data stream */ diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.schema.yaml b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.schema.yaml index f0eea8190f594..66c3e3207634e 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.schema.yaml +++ b/x-pack/platform/plugins/shared/automatic_import_v2/common/model/common_attributes.schema.yaml @@ -37,12 +37,18 @@ components: allOf: - type: object properties: + data_stream_id: + description: The data stream id + $ref: '../../common/model/primitive.schema.yaml#/components/schemas/NonEmptyString' title: description: The title of the data stream $ref: '../../common/model/primitive.schema.yaml#/components/schemas/NonEmptyString' description: description: The description of the data stream $ref: '../../common/model/primitive.schema.yaml#/components/schemas/NonEmptyString' + status: + description: The status of the data stream # TODO: Add enum + $ref: '../../common/model/primitive.schema.yaml#/components/schemas/NonEmptyString' inputTypes: type: array description: The input types of the data stream diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/integration_tests/saved_objects_service.test.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/integration_tests/saved_objects_service.test.ts index 0577b4e260374..1c78311c20a89 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/server/integration_tests/saved_objects_service.test.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/integration_tests/saved_objects_service.test.ts @@ -17,6 +17,7 @@ import type { IntegrationAttributes, DataStreamAttributes, } from '../services/saved_objects/schemas/types'; +import type { Integration } from '../../common'; import { DATA_STREAM_SAVED_OBJECT_TYPE, INPUT_TYPES, @@ -111,22 +112,24 @@ describe('AutomaticImportSavedObjectService', () => { describe('insertIntegration', () => { it('should create integration with service-managed defaults', async () => { - const result = await savedObjectService.insertIntegration(mockRequest, { - ...mockIntegrationData, + const integrationData: Integration = { integration_id: 'test-integration-1', - }); + title: mockIntegrationData.metadata?.title, + description: mockIntegrationData.metadata?.description, + }; + + const result = await savedObjectService.insertIntegration(mockRequest, integrationData); expect(result.id).toBe('test-integration-1'); expect(result.attributes.created_by).toBe('test-user'); expect(result.attributes.metadata?.created_at).toBeDefined(); - expect(result.attributes.metadata?.version).toBe('0.0.0'); + expect(result.attributes.metadata?.version).toBe('1.0.0'); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-integration-1'); }); it('should throw error when integration_id is missing', async () => { - const invalidData = { - ...mockIntegrationData, + const invalidData: Integration = { integration_id: '', }; @@ -136,9 +139,9 @@ describe('AutomaticImportSavedObjectService', () => { }); it('should throw error when integration already exists', async () => { - const integrationData: IntegrationAttributes = { - ...mockIntegrationData, + const integrationData: Integration = { integration_id: 'duplicate-integration', + title: 'Test Integration', }; await savedObjectService.insertIntegration(mockRequest, integrationData); @@ -155,17 +158,17 @@ describe('AutomaticImportSavedObjectService', () => { describe('getIntegration', () => { it('should retrieve an existing integration', async () => { - await savedObjectService.insertIntegration(mockRequest, { - ...mockIntegrationData, + const integrationData: Integration = { integration_id: 'test-get-integration', - data_stream_count: 5, - metadata: { ...mockIntegrationData.metadata, title: 'Get Test Integration' }, - }); + title: 'Get Test Integration', + }; + + await savedObjectService.insertIntegration(mockRequest, integrationData); const result = await savedObjectService.getIntegration('test-get-integration'); expect(result.id).toBe('test-get-integration'); - expect(result.attributes.data_stream_count).toBe(5); + expect(result.attributes.data_stream_count).toBe(0); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-get-integration'); }); @@ -179,55 +182,65 @@ describe('AutomaticImportSavedObjectService', () => { describe('updateIntegration', () => { it('should update an existing integration', async () => { - await savedObjectService.insertIntegration(mockRequest, { - ...mockIntegrationData, + const integrationData: Integration = { integration_id: 'test-update-integration', - data_stream_count: 1, - metadata: { ...mockIntegrationData.metadata, title: 'Original Title' }, - }); + title: 'Original Title', + }; + + await savedObjectService.insertIntegration(mockRequest, integrationData); const result = await savedObjectService.updateIntegration( { - ...mockIntegrationData, integration_id: 'test-update-integration', data_stream_count: 3, + created_by: 'test-user', status: TASK_STATUSES.completed, - metadata: { ...mockIntegrationData.metadata, title: 'Updated Title' }, + metadata: { title: 'Updated Title', version: '1.0.0' }, }, - '0.0.0' + '1.0.0' ); expect(result.attributes.data_stream_count).toBe(3); expect(result.attributes.status).toBe(TASK_STATUSES.completed); - expect(result.attributes.metadata?.version).toBe('0.0.1'); + expect(result.attributes.metadata?.version).toBe('1.0.1'); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-update-integration'); }); it('should increment version on update', async () => { - const integrationData: IntegrationAttributes = { - ...mockIntegrationData, + const integrationData: Integration = { integration_id: 'test-version-integration', - data_stream_count: 1, }; // Create integration const created = await savedObjectService.insertIntegration(mockRequest, integrationData); - expect(created.attributes.metadata?.version).toBe('0.0.0'); + expect(created.attributes.metadata?.version).toBe('1.0.0'); // First update const firstUpdate = await savedObjectService.updateIntegration( - { ...integrationData, data_stream_count: 2 }, - '0.0.0' + { + integration_id: 'test-version-integration', + data_stream_count: 2, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0' ); - expect(firstUpdate.attributes.metadata?.version).toBe('0.0.1'); + expect(firstUpdate.attributes.metadata?.version).toBe('1.0.1'); // Second update const secondUpdate = await savedObjectService.updateIntegration( - { ...integrationData, data_stream_count: 3 }, - '0.0.1' + { + integration_id: 'test-version-integration', + data_stream_count: 3, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.1' }, + }, + '1.0.1' ); - expect(secondUpdate.attributes.metadata?.version).toBe('0.0.2'); + expect(secondUpdate.attributes.metadata?.version).toBe('1.0.2'); // Cleanup await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-version-integration'); @@ -260,126 +273,182 @@ describe('AutomaticImportSavedObjectService', () => { }); it('should throw version conflict when expectedVersion does not match', async () => { - const integrationData: IntegrationAttributes = { + const integrationData: Integration = { integration_id: 'test-app-version-conflict', - data_stream_count: 1, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, }; await savedObjectService.insertIntegration(mockRequest, integrationData); await savedObjectService.updateIntegration( - { ...integrationData, data_stream_count: 2 }, - '0.0.0' + { + integration_id: 'test-app-version-conflict', + data_stream_count: 2, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0' ); // Meta data version increments automatically as patch version await expect( savedObjectService.updateIntegration( - { ...integrationData, data_stream_count: 3 }, - '0.0.0' + { + integration_id: 'test-app-version-conflict', + data_stream_count: 3, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0' ) ).rejects.toThrow( - 'Version conflict: Integration test-app-version-conflict has been updated. Expected version 0.0.0, but current version is 0.0.1' + 'Version conflict: Integration test-app-version-conflict has been updated. Expected version 1.0.0, but current version is 1.0.1' ); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-app-version-conflict'); }); it('should increment major version', async () => { - const integrationData: IntegrationAttributes = { + const integrationData: Integration = { integration_id: 'test-major-version', - data_stream_count: 1, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, }; await savedObjectService.insertIntegration(mockRequest, integrationData); const updated = await savedObjectService.updateIntegration( - { ...integrationData, data_stream_count: 2 }, - '0.0.0', + { + integration_id: 'test-major-version', + data_stream_count: 2, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0', 'major' ); - expect(updated.attributes.metadata?.version).toBe('1.0.0'); + expect(updated.attributes.metadata?.version).toBe('2.0.0'); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-major-version'); }); it('should increment minor version', async () => { - const integrationData: IntegrationAttributes = { + const integrationData: Integration = { integration_id: 'test-minor-version', - data_stream_count: 1, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, }; await savedObjectService.insertIntegration(mockRequest, integrationData); const updated = await savedObjectService.updateIntegration( - { ...integrationData, data_stream_count: 2 }, - '0.0.0', + { + integration_id: 'test-minor-version', + data_stream_count: 2, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0', 'minor' ); - expect(updated.attributes.metadata?.version).toBe('0.1.0'); + expect(updated.attributes.metadata?.version).toBe('1.1.0'); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-minor-version'); }); it('should reset minor and patch when incrementing major version', async () => { - const integrationData: IntegrationAttributes = { + const integrationData: Integration = { integration_id: 'test-major-reset', - data_stream_count: 1, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, }; await savedObjectService.insertIntegration(mockRequest, integrationData); - await savedObjectService.updateIntegration({ ...integrationData }, '0.0.0', 'minor'); + await savedObjectService.updateIntegration( + { + integration_id: 'test-major-reset', + data_stream_count: 1, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0', + 'minor' + ); - await savedObjectService.updateIntegration({ ...integrationData }, '0.1.0', 'patch'); + await savedObjectService.updateIntegration( + { + integration_id: 'test-major-reset', + data_stream_count: 1, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.1.0' }, + }, + '1.1.0', + 'patch' + ); const updated = await savedObjectService.updateIntegration( - { ...integrationData }, - '0.1.1', + { + integration_id: 'test-major-reset', + data_stream_count: 1, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.1.1' }, + }, + '1.1.1', 'major' ); - expect(updated.attributes.metadata?.version).toBe('1.0.0'); + expect(updated.attributes.metadata?.version).toBe('2.0.0'); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-major-reset'); }); it('should reset patch when incrementing minor version', async () => { - const integrationData: IntegrationAttributes = { + const integrationData: Integration = { integration_id: 'test-minor-reset', - data_stream_count: 1, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, }; await savedObjectService.insertIntegration(mockRequest, integrationData); - await savedObjectService.updateIntegration({ ...integrationData }, '0.0.0', 'patch'); + await savedObjectService.updateIntegration( + { + integration_id: 'test-minor-reset', + data_stream_count: 1, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.0' }, + }, + '1.0.0', + 'patch' + ); - await savedObjectService.updateIntegration({ ...integrationData }, '0.0.1', 'patch'); + await savedObjectService.updateIntegration( + { + integration_id: 'test-minor-reset', + data_stream_count: 1, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.1' }, + }, + '1.0.1', + 'patch' + ); const updated = await savedObjectService.updateIntegration( - { ...integrationData }, - '0.0.2', + { + integration_id: 'test-minor-reset', + data_stream_count: 1, + created_by: 'test-user', + status: TASK_STATUSES.pending, + metadata: { version: '1.0.2' }, + }, + '1.0.2', 'minor' ); - expect(updated.attributes.metadata?.version).toBe('0.1.0'); + expect(updated.attributes.metadata?.version).toBe('1.1.0'); await savedObjectsClient.delete(INTEGRATION_SAVED_OBJECT_TYPE, 'test-minor-reset'); }); @@ -388,20 +457,12 @@ describe('AutomaticImportSavedObjectService', () => { describe('getAllIntegrations', () => { it('should retrieve all integrations', async () => { // Create multiple integrations - const integration1: IntegrationAttributes = { + const integration1: Integration = { integration_id: 'test-getall-1', - data_stream_count: 1, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, }; - const integration2: IntegrationAttributes = { + const integration2: Integration = { integration_id: 'test-getall-2', - data_stream_count: 2, - created_by: 'test-user', - status: TASK_STATUSES.completed, - metadata: {}, }; try { @@ -410,8 +471,12 @@ describe('AutomaticImportSavedObjectService', () => { const result = await savedObjectService.getAllIntegrations(); - expect(result.total).toBe(2); - const ids = result.saved_objects.map((obj) => obj.id); + // Filter to only the test integrations to avoid pollution from other tests + const testIntegrations = result.saved_objects.filter( + (obj) => obj.id === 'test-getall-1' || obj.id === 'test-getall-2' + ); + expect(testIntegrations.length).toBe(2); + const ids = testIntegrations.map((obj) => obj.id); expect(ids).toContain('test-getall-1'); expect(ids).toContain('test-getall-2'); } finally { @@ -443,12 +508,10 @@ describe('AutomaticImportSavedObjectService', () => { const integrationId = 'test-cascade-delete-integration'; // Create integrations for testing cascade deletion - await savedObjectService.insertIntegration(mockRequest, { + const integrationData: Integration = { integration_id: integrationId, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, - }); + }; + await savedObjectService.insertIntegration(mockRequest, integrationData); await savedObjectService.insertDataStream(mockRequest, { integration_id: integrationId, @@ -532,12 +595,10 @@ describe('AutomaticImportSavedObjectService', () => { describe('insertDataStream', () => { it('should create a new data stream with existing integration', async () => { - await savedObjectService.insertIntegration(mockRequest, { + const integrationData: Integration = { integration_id: 'test-ds-integration-1', - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, - }); + }; + await savedObjectService.insertIntegration(mockRequest, integrationData); const result = await savedObjectService.insertDataStream(mockRequest, { ...mockDataStreamData, @@ -959,10 +1020,10 @@ describe('AutomaticImportSavedObjectService', () => { await savedObjectService.insertDataStream(mockRequest, dataStream1); await savedObjectService.insertDataStream(mockRequest, dataStream2); - const result = await savedObjectService.getAllDataStreams(); + const result = await savedObjectService.getAllDataStreams('getall-ds-integration'); - // Check that we have at least the 2 data streams we created - expect(result.total).toBeGreaterThanOrEqual(2); + // Check that we have the 2 data streams we created + expect(result.total).toBe(2); const ids = result.saved_objects.map((obj) => obj.id); expect(ids).toContain('test-getall-ds-1'); expect(ids).toContain('test-getall-ds-2'); @@ -1086,12 +1147,10 @@ describe('AutomaticImportSavedObjectService', () => { const integrationId = 'delete-multiple-ds-integration'; // Create integration - await savedObjectService.insertIntegration(mockRequest, { + const integrationData: Integration = { integration_id: integrationId, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: {}, - }); + }; + await savedObjectService.insertIntegration(mockRequest, integrationData); // Create 3 data streams await savedObjectService.insertDataStream(mockRequest, { @@ -1182,15 +1241,9 @@ describe('AutomaticImportSavedObjectService', () => { it('should handle complete workflow: create integration, add data streams, update, and query', async () => { const integrationId = 'workflow-integration'; - const integrationData: IntegrationAttributes = { + const integrationData: Integration = { integration_id: integrationId, - data_stream_count: 0, - created_by: 'test-user', - status: TASK_STATUSES.pending, - metadata: { - title: 'Workflow Test Integration', - version: '0.0.0', - }, + title: 'Workflow Test Integration', }; const createdIntegration = await savedObjectService.insertIntegration( mockRequest, @@ -1257,10 +1310,10 @@ describe('AutomaticImportSavedObjectService', () => { status: TASK_STATUSES.completed, metadata: { title: 'Workflow Test Integration - Completed', - version: '0.0.2', + version: integration.attributes.metadata?.version || '1.0.0', }, }, - integration.attributes.metadata?.version || '0.0.0' + integration.attributes.metadata?.version || '1.0.0' ); const finalIntegration = await savedObjectService.getIntegration(integrationId); diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/plugin.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/plugin.ts index be2c58f1e0196..90e779365bac0 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/server/plugin.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/plugin.ts @@ -5,15 +5,9 @@ * 2.0. */ -import type { - PluginInitializerContext, - CoreStart, - Plugin, - Logger, - ElasticsearchClient, -} from '@kbn/core/server'; - -import { ReplaySubject, type Subject } from 'rxjs'; +import type { PluginInitializerContext, CoreStart, Plugin, Logger } from '@kbn/core/server'; +import { ReplaySubject } from 'rxjs'; +import { registerRoutes } from './routes'; import type { AutomaticImportV2PluginCoreSetupDependencies, AutomaticImportV2PluginSetup, @@ -35,7 +29,7 @@ export class AutomaticImportV2Plugin > { private readonly logger: Logger; - private pluginStop$: Subject; + private readonly pluginStop$: ReplaySubject; private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; private automaticImportService: AutomaticImportService | null = null; @@ -58,7 +52,7 @@ export class AutomaticImportV2Plugin this.logger.debug('automaticImportV2: Setup'); const coreStartServices = core.getStartServices().then(([coreStart]) => ({ - esClient: coreStart.elasticsearch.client.asInternalUser as ElasticsearchClient, + esClient: coreStart.elasticsearch.client.asInternalUser, })); const esClientPromise = coreStartServices.then(({ esClient }) => esClient); @@ -80,6 +74,11 @@ export class AutomaticImportV2Plugin AutomaticImportV2PluginRequestHandlerContext, 'automaticImportv2' >('automaticImportv2', (context, request) => requestContextFactory.create(context, request)); + + const router = core.http.createRouter(); + this.logger.debug('automaticImportV2 api: Setup'); + registerRoutes(router, this.logger); + return { actions: plugins.actions, }; diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/index.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/index.ts new file mode 100644 index 0000000000000..ebc597350969e --- /dev/null +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { registerRoutes } from './register_routes'; diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/integrations_route.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/integrations_route.ts new file mode 100644 index 0000000000000..018098ddc73e6 --- /dev/null +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/integrations_route.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IRouter, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { AutomaticImportV2PluginRequestHandlerContext } from '../types'; +import type { + CreateAutoImportIntegrationResponse, + GetAutoImportIntegrationsResponse, + GetAutoImportIntegrationResponse, +} from '../../common'; +import { + CreateAutoImportIntegrationRequestBody, + GetAutoImportIntegrationRequestParams, +} from '../../common'; +import { buildAutomaticImportV2Response } from './utils'; + +export const registerIntegrationRoutes = ( + router: IRouter, + logger: Logger +) => { + getAllIntegrationsRoute(router, logger); + getIntegrationByIdRoute(router, logger); + addIntegrationRoute(router, logger); +}; + +const getAllIntegrationsRoute = ( + router: IRouter, + logger: Logger +) => + router.versioned + .get({ + access: 'internal', + path: '/api/automatic_import_v2/integrations', + security: { + authz: { + requiredPrivileges: ['securitySolution', 'automaticImportv2'], + }, + }, + }) + .addVersion( + { + version: '1', + validate: false, + }, + async (context, _, response) => { + try { + const automaticImportv2 = await context.automaticImportv2; + const automaticImportService = automaticImportv2.automaticImportService; + const integrations = await automaticImportService.getAllIntegrations(); + const body: GetAutoImportIntegrationsResponse = integrations; + return response.ok({ body }); + } catch (err) { + logger.error(`registerIntegrationRoutes: Caught error:`, err); + const automaticImportV2Response = buildAutomaticImportV2Response(response); + return automaticImportV2Response.error({ + statusCode: 500, + body: err, + }); + } + } + ); + +const getIntegrationByIdRoute = ( + router: IRouter, + logger: Logger +) => + router.versioned + .get({ + access: 'internal', + path: '/api/automatic_import_v2/integrations/{integration_id}', + security: { + authz: { + requiredPrivileges: ['securitySolution', 'automaticImportv2'], + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: buildRouteValidationWithZod(GetAutoImportIntegrationRequestParams), + }, + }, + }, + async (context, request, response) => { + try { + const automaticImportv2 = await context.automaticImportv2; + const automaticImportService = automaticImportv2.automaticImportService; + const { integration_id: integrationId } = request.params; + const integration = await automaticImportService.getIntegrationById(integrationId); + const body: GetAutoImportIntegrationResponse = { integration }; + return response.ok({ body }); + } catch (err) { + logger.error(`getIntegrationByIdRoute: Caught error:`, err); + const automaticImportV2Response = buildAutomaticImportV2Response(response); + return automaticImportV2Response.error({ + statusCode: 500, + body: err, + }); + } + } + ); + +const addIntegrationRoute = ( + router: IRouter, + logger: Logger +) => + router.versioned + .put({ + access: 'internal', + path: '/api/automatic_import_v2/integrations', + security: { + authz: { + requiredPrivileges: ['securitySolution', 'automaticImportv2'], + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(CreateAutoImportIntegrationRequestBody), + }, + }, + }, + async (context, request, response) => { + try { + const automaticImportv2 = await context.automaticImportv2; + const automaticImportService = automaticImportv2.automaticImportService; + const integrationRequestBody = request.body; + const integrationId = createId(integrationRequestBody.title); + const integrationData = { + integration_id: integrationId, + dataStreams: integrationRequestBody.dataStreams, + title: integrationRequestBody.title, + logo: integrationRequestBody.logo, + description: integrationRequestBody.description, + }; + await automaticImportService.insertIntegration(request, integrationData); + // TODO: Create data streams + const body: CreateAutoImportIntegrationResponse = { integration_id: integrationId }; + return response.ok({ body }); + } catch (err) { + logger.error(`addIntegrationRoute: Caught error:`, err); + const automaticImportV2Response = buildAutomaticImportV2Response(response); + return automaticImportV2Response.error({ + statusCode: 500, + body: err, + }); + } + } + ); + +function createId(title: string): string { + return title.toLowerCase().replace(/ /g, '-'); +} diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/register_routes.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/register_routes.ts new file mode 100644 index 0000000000000..6dab7af5b5415 --- /dev/null +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/register_routes.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IRouter, Logger } from '@kbn/core/server'; +import type { AutomaticImportV2PluginRequestHandlerContext } from '../types'; +import { registerIntegrationRoutes } from './integrations_route'; + +export function registerRoutes( + router: IRouter, + logger: Logger +) { + registerIntegrationRoutes(router, logger); +} diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/utils.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/utils.ts new file mode 100644 index 0000000000000..cd1339b55cf78 --- /dev/null +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/routes/utils.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + KibanaResponseFactory, + CustomHttpResponseOptions, + HttpResponsePayload, + ResponseError, +} from '@kbn/core/server'; + +const statusToErrorMessage = (statusCode: number) => { + switch (statusCode) { + case 400: + return 'Bad Request'; + case 401: + return 'Unauthorized'; + case 403: + return 'Forbidden'; + case 404: + return 'Not Found'; + case 409: + return 'Conflict'; + case 500: + return 'Internal Error'; + default: + return '(unknown error)'; + } +}; + +export class AutomaticImportV2ResponseFactory { + constructor(private response: KibanaResponseFactory) {} + + error({ + statusCode, + body, + headers, + }: CustomHttpResponseOptions) { + const contentType: CustomHttpResponseOptions['headers'] = { + 'content-type': 'application/json', + }; + const defaultedHeaders: CustomHttpResponseOptions['headers'] = { + ...contentType, + ...(headers ?? {}), + }; + + return this.response.custom({ + headers: defaultedHeaders, + statusCode, + body: Buffer.from( + JSON.stringify({ + message: body ?? statusToErrorMessage(statusCode), + status_code: statusCode, + }) + ), + }); + } +} + +export const buildAutomaticImportV2Response = (response: KibanaResponseFactory) => + new AutomaticImportV2ResponseFactory(response); diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.test.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.test.ts index 006d264183f96..bd929d9bc641e 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.test.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.test.ts @@ -117,7 +117,7 @@ describe('AutomaticImportSetupService', () => { }); it('should throw error when calling getIntegration before initialize', async () => { - await expect(service.getIntegration('test-id')).rejects.toThrow( + await expect(service.getIntegrationById('test-id')).rejects.toThrow( 'Saved Objects service not initialized.' ); }); diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.ts index 7ba79f9f9d090..8d55b970639c9 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/automatic_import_service.ts @@ -20,7 +20,7 @@ import type { SavedObjectsServiceSetup, SavedObjectsServiceStart, } from '@kbn/core/server'; -import type { DataStreamSamples } from '../../common'; +import type { DataStreamSamples, Integration, DataStream } from '../../common'; import type { IntegrationAttributes, DataStreamAttributes } from './saved_objects/schemas/types'; import { AutomaticImportSamplesIndexService } from './samples_index/index_service'; import { getAuthenticatedUser } from './lib/get_user'; @@ -74,7 +74,7 @@ export class AutomaticImportService { public async insertIntegration( request: KibanaRequest, - data: IntegrationAttributes, + data: Integration, options?: SavedObjectsCreateOptions ): Promise> { if (!this.savedObjectService) { @@ -95,18 +95,38 @@ export class AutomaticImportService { return this.savedObjectService.updateIntegration(data, expectedVersion, versionUpdate, options); } - public async getIntegration(integrationId: string): Promise> { + public async getIntegrationById(integrationId: string): Promise { if (!this.savedObjectService) { throw new Error('Saved Objects service not initialized.'); } - return this.savedObjectService.getIntegration(integrationId); + const integration = await this.savedObjectService.getIntegration(integrationId); + const dataStreams = await this.getAllDataStreams(integrationId); + return { + integration_id: integrationId, + dataStreams, + title: integration.attributes.metadata.title, + logo: integration.attributes.metadata.logo, + description: integration.attributes.metadata.description, + }; } - public async getAllIntegrations(): Promise> { + public async getAllIntegrations(): Promise { if (!this.savedObjectService) { throw new Error('Saved Objects service not initialized.'); } - return this.savedObjectService.getAllIntegrations(); + const integrations = await this.savedObjectService.getAllIntegrations(); + return Promise.all( + integrations.saved_objects.map(async (integration) => { + const dataStreams = await this.getAllDataStreams(integration.attributes.integration_id); + return { + integration_id: integration.attributes.integration_id, + dataStreams, + title: integration.attributes.metadata.title, + logo: integration.attributes.metadata.logo, + description: integration.attributes.metadata.description, + }; + }) + ); } public async deleteIntegration( @@ -153,11 +173,16 @@ export class AutomaticImportService { return this.savedObjectService.getDataStream(dataStreamId); } - public async getAllDataStreams(): Promise> { + public async getAllDataStreams(integrationId: string): Promise { if (!this.savedObjectService) { throw new Error('Saved Objects service not initialized.'); } - return this.savedObjectService.getAllDataStreams(); + const dataStreams = await this.savedObjectService.getAllDataStreams(integrationId); + return dataStreams.saved_objects.map((dataStream) => ({ + dataStreamId: dataStream.attributes.data_stream_id, + status: dataStream.attributes.job_info.status, + metadata: dataStream.attributes.metadata, + })); } public async findAllDataStreamsByIntegrationId( diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/saved_objects_service.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/saved_objects_service.ts index 82bacd74402ef..9c391906c8ae7 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/saved_objects_service.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/saved_objects_service.ts @@ -24,6 +24,7 @@ import { INTEGRATION_SAVED_OBJECT_TYPE, TASK_STATUSES, } from './constants'; +import type { Integration } from '../../../common'; export class AutomaticImportSavedObjectService { private savedObjectsClient: SavedObjectsClientContract; @@ -97,21 +98,16 @@ export class AutomaticImportSavedObjectService { */ public async insertIntegration( request: KibanaRequest, - data: IntegrationAttributes, + data: Integration, options?: SavedObjectsCreateOptions ): Promise> { const authenticatedUser = this.security?.authc.getCurrentUser(request); if (!authenticatedUser) { throw new Error('No user authenticated'); } + const integrationId = data.integration_id; - const { - integration_id: integrationId, - data_stream_count: dataStreamCount = 0, - metadata = {}, - } = data; - - if (!integrationId) { + if (!integrationId || integrationId.trim() === '') { throw new Error('Integration ID is required'); } @@ -120,13 +116,15 @@ export class AutomaticImportSavedObjectService { const initialIntegrationData: IntegrationAttributes = { integration_id: integrationId, - data_stream_count: dataStreamCount, + data_stream_count: data.dataStreams?.length || 0, created_by: authenticatedUser.username, status: TASK_STATUSES.pending, metadata: { - ...metadata, + title: data.title, + logo: data.logo, + description: data.description, created_at: new Date().toISOString(), - version: '0.0.0', + version: '1.0.0', }, }; @@ -493,19 +491,28 @@ export class AutomaticImportSavedObjectService { `Data stream created successfully, creating new integration ${integrationId}` ); - const defaultIntegrationData: IntegrationAttributes = { + const defaultIntegrationData: Integration = { integration_id: integrationId, - data_stream_count: 1, - created_by: authenticatedUser.username, - status: TASK_STATUSES.pending, - metadata: { - created_at: new Date().toISOString(), - version: '0.0.0', - title: `Auto-generated integration ${integrationId}`, - }, + dataStreams: [], // Empty array, data_stream_count will be set correctly below + title: `Auto-generated integration ${integrationId}`, }; - await this.insertIntegration(request, defaultIntegrationData); + const createdIntegration = await this.insertIntegration(request, defaultIntegrationData); + + // Update the data_stream_count to 1 since we just created a data stream + // Need to preserve all metadata fields when updating + await this.updateIntegration( + { + integration_id: integrationId, + data_stream_count: 1, + created_by: createdIntegration.attributes.created_by, + status: createdIntegration.attributes.status, + metadata: { + ...createdIntegration.attributes.metadata, + }, + }, + createdIntegration.attributes.metadata?.version || '1.0.0' + ); } } catch (integrationError) { this.logger.error( @@ -627,14 +634,21 @@ export class AutomaticImportSavedObjectService { * Get all data streams * @returns All data streams */ - public async getAllDataStreams(): Promise> { + public async getAllDataStreams( + integrationId: string + ): Promise> { try { this.logger.debug('Getting all data streams'); return await this.savedObjectsClient.find({ type: DATA_STREAM_SAVED_OBJECT_TYPE, + filter: `${DATA_STREAM_SAVED_OBJECT_TYPE}.attributes.integration_id: ${JSON.stringify( + integrationId + )}`, }); } catch (error) { - this.logger.error(`Failed to get all data streams: ${error}`); + this.logger.error( + `Failed to get all data streams for integration ${integrationId}: ${error}` + ); throw error; } } diff --git a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/schemas/integration_schema.ts b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/schemas/integration_schema.ts index d74cc472e8d42..d3b7db489679b 100644 --- a/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/schemas/integration_schema.ts +++ b/x-pack/platform/plugins/shared/automatic_import_v2/server/services/saved_objects/schemas/integration_schema.ts @@ -19,6 +19,7 @@ export const integrationSchemaV1 = schema.object({ metadata: schema.object( { title: schema.maybe(schema.string()), + logo: schema.maybe(schema.string()), version: schema.maybe( schema.string({ minLength: 5, diff --git a/x-pack/platform/test/automatic_import_v2_api_integration/apis/index.ts b/x-pack/platform/test/automatic_import_v2_api_integration/apis/index.ts new file mode 100644 index 0000000000000..d07346c72c17a --- /dev/null +++ b/x-pack/platform/test/automatic_import_v2_api_integration/apis/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AutomaticImportV2ApiFtrProviderContext } from '../services/api'; + +export default function ({ loadTestFile, getService }: AutomaticImportV2ApiFtrProviderContext) { + describe('Automatic Import V2 Endpoints', function () { + loadTestFile(require.resolve('./integrations')); + }); +} diff --git a/x-pack/platform/test/automatic_import_v2_api_integration/apis/integrations.ts b/x-pack/platform/test/automatic_import_v2_api_integration/apis/integrations.ts new file mode 100644 index 0000000000000..7783a986ce733 --- /dev/null +++ b/x-pack/platform/test/automatic_import_v2_api_integration/apis/integrations.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import type { AutomaticImportV2ApiFtrProviderContext } from '../services/api'; +import { createAutomaticImportV2ApiClient } from '../utils/automatic_import_v2_client'; + +export default function ({ getService }: AutomaticImportV2ApiFtrProviderContext) { + const supertest = getService('supertest'); + const automaticImportV2Client = createAutomaticImportV2ApiClient(supertest); + + describe('Automatic Import V2 Integration Routes', function () { + describe('GET /api/automatic_import_v2/integrations', function () { + it('should return empty array when no integrations exist', async function () { + const response = await automaticImportV2Client.getAllIntegrations(); + expect(response).to.be.an('array'); + expect(response.length).to.eql(0); + }); + + it('should return all integrations even without data streams', async function () { + // Create an integration first + const createResponse = await automaticImportV2Client.createIntegration({ + title: 'Test Integration', + description: 'A test integration', + }); + + const integrationId = createResponse.integration_id; + expect(integrationId).to.be.a('string'); + expect(integrationId).to.eql('test-integration'); + + // Get all integrations + const allIntegrations = await automaticImportV2Client.getAllIntegrations(); + expect(allIntegrations).to.be.an('array'); + expect(allIntegrations.length).to.be.greaterThan(0); + + const integration = allIntegrations.find((i) => i.integration_id === integrationId); + expect(integration).to.be.ok(); + expect(integration?.title).to.eql('Test Integration'); + expect(integration?.description).to.eql('A test integration'); + }); + }); + + describe('GET /api/automatic_import_v2/integrations/{integration_id}', function () { + it('should return integration by id even without data streams', async function () { + // Create an integration first + const createResponse = await automaticImportV2Client.createIntegration({ + title: 'Get By Id Test', + description: 'Testing get by id', + logo: 'data:image/png;base64,test', + }); + + const integrationId = createResponse.integration_id; + if (!integrationId) { + throw new Error('Integration ID is missing'); + } + + // Get the integration by id + const response = await automaticImportV2Client.getIntegrationById(integrationId); + expect(response.integration).to.be.ok(); + expect(response.integration.integration_id).to.eql(integrationId); + expect(response.integration.title).to.eql('Get By Id Test'); + expect(response.integration.description).to.eql('Testing get by id'); + expect(response.integration.logo).to.eql('data:image/png;base64,test'); + }); + + it('should return error for non-existent integration', async function () { + const res = await supertest + .get('/api/automatic_import_v2/integrations/non-existent-id') + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'automatic-import-v2-test') + .set(ELASTIC_HTTP_VERSION_HEADER, '1'); + expect(res.status).to.be(500); + }); + }); + + describe('PUT /api/automatic_import_v2/integrations', function () { + it('should create a new integration without data streams', async function () { + const integrationData = { + title: 'New Integration', + description: 'A new integration for testing', + logo: 'data:image/png;base64,logo123', + }; + + const response = await automaticImportV2Client.createIntegration(integrationData); + expect(response.integration_id).to.be.a('string'); + expect(response.integration_id).to.eql('new-integration'); + }); + }); + }); +} diff --git a/x-pack/platform/test/automatic_import_v2_api_integration/configs/config.stateful.ts b/x-pack/platform/test/automatic_import_v2_api_integration/configs/config.stateful.ts new file mode 100644 index 0000000000000..126e3d19288a4 --- /dev/null +++ b/x-pack/platform/test/automatic_import_v2_api_integration/configs/config.stateful.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createStatefulTestConfig } from '../../api_integration_deployment_agnostic/default_configs/stateful.config.base'; +import { automaticImportV2ApiServices } from '../services/api'; + +export default createStatefulTestConfig({ + services: automaticImportV2ApiServices, + testFiles: [require.resolve('../apis')], + junit: { + reportName: 'X-Pack Automatic Import V2 Stateful API Integration Tests', + }, + // @ts-expect-error + kbnTestServer: { + serverArgs: [ + '--xpack.automatic_import_v2.enabled=true', + `--logging.loggers=${JSON.stringify([ + { + name: 'plugins.automaticImportV2', + level: 'all', + appenders: ['default'], + }, + ])}`, + ], + }, +}); diff --git a/x-pack/platform/test/automatic_import_v2_api_integration/services/api.ts b/x-pack/platform/test/automatic_import_v2_api_integration/services/api.ts new file mode 100644 index 0000000000000..d758304102b51 --- /dev/null +++ b/x-pack/platform/test/automatic_import_v2_api_integration/services/api.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { GenericFtrProviderContext } from '@kbn/test'; +import { automaticImportV2CommonServices } from './common'; + +/** + * Automatic Import V2 API-only services. + * Composes common deployment-agnostic services and adds any automatic_import_v2-specific API helpers. + */ +export const automaticImportV2ApiServices = { + ...automaticImportV2CommonServices, +}; + +export type AutomaticImportV2ApiFtrProviderContext = GenericFtrProviderContext< + typeof automaticImportV2ApiServices, + {} +>; diff --git a/x-pack/platform/test/automatic_import_v2_api_integration/services/common.ts b/x-pack/platform/test/automatic_import_v2_api_integration/services/common.ts new file mode 100644 index 0000000000000..c2a0d44577090 --- /dev/null +++ b/x-pack/platform/test/automatic_import_v2_api_integration/services/common.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { services as platformDeploymentAgnosticServices } from '../../api_integration_deployment_agnostic/services'; + +/** + * Services common to both API and UI automatic_import_v2 test suites. + */ +export const automaticImportV2CommonServices = { + ...platformDeploymentAgnosticServices, +}; + +export type AutomaticImportV2CommonServices = typeof automaticImportV2CommonServices; diff --git a/x-pack/platform/test/automatic_import_v2_api_integration/utils/automatic_import_v2_client.ts b/x-pack/platform/test/automatic_import_v2_api_integration/utils/automatic_import_v2_client.ts new file mode 100644 index 0000000000000..5b1579ee888f5 --- /dev/null +++ b/x-pack/platform/test/automatic_import_v2_api_integration/utils/automatic_import_v2_client.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Agent } from 'supertest'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import type { + CreateAutoImportIntegrationRequestBody, + CreateAutoImportIntegrationResponse, + GetAutoImportIntegrationsResponse, + GetAutoImportIntegrationResponse, +} from '@kbn/automatic-import-v2-plugin/common'; + +export function createAutomaticImportV2ApiClient(supertest: Agent) { + return { + /** + * Get all integrations + */ + async getAllIntegrations(): Promise { + const res = await supertest + .get('/api/automatic_import_v2/integrations') + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'automatic-import-v2-test') + .set(ELASTIC_HTTP_VERSION_HEADER, '1'); + return res.body; + }, + + /** + * Get integration by ID + */ + async getIntegrationById(integrationId: string): Promise { + const res = await supertest + .get(`/api/automatic_import_v2/integrations/${integrationId}`) + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'automatic-import-v2-test') + .set(ELASTIC_HTTP_VERSION_HEADER, '1'); + return res.body; + }, + + /** + * Create or update an integration + */ + async createIntegration( + payload: CreateAutoImportIntegrationRequestBody + ): Promise { + const res = await supertest + .put('/api/automatic_import_v2/integrations') + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'automatic-import-v2-test') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .send(payload); + return res.body; + }, + }; +} + +export type AutomaticImportV2ApiClient = ReturnType;