diff --git a/packages/spacecat-shared-data-access/docs/schema.json b/packages/spacecat-shared-data-access/docs/schema.json index cbac61c4..6e6f92af 100644 --- a/packages/spacecat-shared-data-access/docs/schema.json +++ b/packages/spacecat-shared-data-access/docs/schema.json @@ -1000,6 +1000,22 @@ "Projection": { "ProjectionType": "ALL" } + }, + { + "IndexName": "spacecat-services-all-import-jobs-by-date-range", + "KeyAttributes": { + "PartitionKey": { + "AttributeName": "GSI1PK", + "AttributeType": "S" + }, + "SortKey": { + "AttributeName": "startTime", + "AttributeType": "S" + } + }, + "Projection": { + "ProjectionType": "ALL" + } } ], "DataAccess": { diff --git a/packages/spacecat-shared-data-access/src/dto/import-job.js b/packages/spacecat-shared-data-access/src/dto/import-job.js index e9d860af..c5ee34fa 100644 --- a/packages/spacecat-shared-data-access/src/dto/import-job.js +++ b/packages/spacecat-shared-data-access/src/dto/import-job.js @@ -35,8 +35,6 @@ export const ImportJobDto = { duration: importJob.getDuration(), status: importJob.getStatus(), urlCount: importJob.getUrlCount(), - successCount: importJob.getSuccessCount(), - failedCount: importJob.getFailedCount(), importQueueId: importJob.getImportQueueId(), initiatedBy: importJob.getInitiatedBy(), GSI1PK: 'ALL_IMPORT_JOBS', @@ -57,12 +55,11 @@ export const ImportJobDto = { duration: dynamoItem.duration, status: dynamoItem.status, urlCount: dynamoItem.urlCount, - successCount: dynamoItem.successCount, - failedCount: dynamoItem.failedCount, importQueueId: dynamoItem.importQueueId, initiatedBy: dynamoItem.initiatedBy, }; return createImportJob(importJobData); }, + }; diff --git a/packages/spacecat-shared-data-access/src/index.d.ts b/packages/spacecat-shared-data-access/src/index.d.ts index fd6a8eef..96361b4c 100644 --- a/packages/spacecat-shared-data-access/src/index.d.ts +++ b/packages/spacecat-shared-data-access/src/index.d.ts @@ -10,6 +10,22 @@ * governing permissions and limitations under the License. */ +// see packages/spacecat-shared-data-access/src/models/importer/import-constants.js +export declare const ImportJobStatus: { + readonly RUNNING: 'RUNNING'; + readonly COMPLETE: 'COMPLETE'; + readonly FAILED: 'FAILED'; +}; + +// packages/spacecat-shared-data-access/src/models/importer/import-constants.js +export declare const ImportUrlStatus: { + readonly PENDING: 'PENDING'; + readonly REDIRECT: 'REDIRECT'; + readonly RUNNING: 'RUNNING'; + readonly COMPLETE: 'COMPLETE'; + readonly FAILED: 'FAILED'; +}; + // TODO: introduce AuditType interface or Scores interface /** @@ -463,7 +479,7 @@ export interface ImportJob { /** * Retrieves the status of the import job. */ - getStatus: () => string; + getStatus: () => typeof ImportJobStatus; /** * Retrieves the baseURL of the import job. @@ -515,28 +531,38 @@ export interface ImportJob { */ getInitiatedBy: () => object; + /** + * Retrieves the progress of the import job. + */ + getProgress: () => { + completed: number; + failed: number; + pending: number; + running: number; + redirect: number; + }; } export interface ImportUrl { /** * Retrieves the ID of the import URL. */ - getId: () => string; + getId: () => string; /** * Retrieves the status of the import URL. */ - getStatus: () => string; + getStatus: () => typeof ImportUrlStatus; /** * Retrieves the URL of the import URL. */ - getUrl: () => string; + getUrl: () => string; /** * Retrieves the job ID of the import URL. */ - getJobId: () => string; + getJobId: () => string; /** * The reason that the import of a URL failed. @@ -867,13 +893,3 @@ export function createDataAccess( config: DataAccessConfig, logger: object, ): DataAccess; - -export interface ImportJobStatus { - RUNNING: string, - COMPLETE: string, - FAILED: string, -} - -export interface ImportUrlStatus extends ImportJobStatus { - PENDING: string, -} diff --git a/packages/spacecat-shared-data-access/src/models/importer/import-constants.js b/packages/spacecat-shared-data-access/src/models/importer/import-constants.js index 643708ef..22282482 100644 --- a/packages/spacecat-shared-data-access/src/models/importer/import-constants.js +++ b/packages/spacecat-shared-data-access/src/models/importer/import-constants.js @@ -15,6 +15,10 @@ export const ImportOptions = { ENABLE_JAVASCRIPT: 'enableJavascript', PAGE_LOAD_TIMEOUT: 'pageLoadTimeout', + SAVE_AS_DOCX: 'saveAsDocx', + HAS_CUSTOM_HEADERS: 'hasCustomHeaders', + HAS_CUSTOM_IMPORT_JS: 'hasCustomImportJs', + SCROLL_TO_BOTTOM: 'scrollToBottom', }; /** diff --git a/packages/spacecat-shared-data-access/src/models/importer/import-job.js b/packages/spacecat-shared-data-access/src/models/importer/import-job.js index 136b7edf..8b548782 100644 --- a/packages/spacecat-shared-data-access/src/models/importer/import-job.js +++ b/packages/spacecat-shared-data-access/src/models/importer/import-job.js @@ -16,6 +16,19 @@ import { import { Base } from '../base.js'; import { ImportJobStatus, ImportOptions } from './import-constants.js'; +export const jobToApiMap = [ + ['id', 'id'], + ['baseURL', 'baseURL'], + ['options', 'options'], + ['startTime', 'startTime'], + ['endTime', 'endTime'], + ['duration', 'duration'], + ['status', 'status'], + ['urlCount', 'urlCount'], + ['initiatedBy', 'initiatedBy'], + ['progress', 'progress'], +]; + /** * Creates a new ImportJob object. * @@ -34,11 +47,18 @@ const ImportJob = (data) => { self.getDuration = () => self.state.duration; self.getStatus = () => self.state.status; self.getUrlCount = () => self.state.urlCount; - self.getSuccessCount = () => self.state.successCount; - self.getFailedCount = () => self.state.failedCount; self.getImportQueueId = () => self.state.importQueueId; self.getInitiatedBy = () => self.state.initiatedBy; + // the progress is a derived property from querying the status of the import urls table + self.getProgress = () => self.state.progress || { + running: 0, + failed: 0, + completed: 0, + pending: 0, + redirect: 0, + }; + /** * Updates the state of the ImportJob. * @param key - The key to update. @@ -97,27 +117,6 @@ const ImportJob = (data) => { } }); - /** - * Updates the success count of the ImportJob. - * @param {number} successCount - The new success count. - */ - self.updateSuccessCount = (successCount) => updateState('successCount', successCount, (value) => { - if (!isInteger(value)) { - throw new Error(`Invalid success count during update: ${value}`); - } - }); - - /** - * Updates the failed count of the ImportJob. - * @param {number} failedCount - The new failed count. - * @returns {{ImportJob}} The updated ImportJob object. - */ - self.updateFailedCount = (failedCount) => updateState('failedCount', failedCount, (value) => { - if (!isInteger(value)) { - throw new Error(`Invalid failed count during update: ${value}`); - } - }); - /** * Updates the import queue id of the ImportJob. * @param {string} importQueueId - The new import queue id. @@ -184,8 +183,10 @@ export const createImportJob = (data) => { throw new Error(`Invalid options: ${newState.options}`); } - const invalidOptions = Object.keys(newState.options) - .filter((key) => !Object.values(ImportOptions).includes(key)); + const invalidOptions = Object.keys(newState.options).filter( + (key) => !Object.values(ImportOptions) + .some((value) => value.toLowerCase() === key.toLowerCase()), + ); if (invalidOptions.length > 0) { throw new Error(`Invalid options: ${invalidOptions}`); diff --git a/packages/spacecat-shared-data-access/src/models/importer/import-model-mapper.js b/packages/spacecat-shared-data-access/src/models/importer/import-model-mapper.js new file mode 100644 index 00000000..b0cad9e8 --- /dev/null +++ b/packages/spacecat-shared-data-access/src/models/importer/import-model-mapper.js @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/** + * Map the properties of the source object into the target object based on the mapping array. + * + * @param {object} sourceObj The source object. + * @param {array} mapping An array of arrays in the format of either + * ['modelPropertyKey', ['apiPropertyKey'] or ['modelPropertyKey', modelToApiFn()] + */ +function mappingReducer(sourceObj, mapping) { + const sourceMapIndex = 0; + const targetMapIndex = 1; + + return mapping.reduce((targetObj, mapEntry) => { + if (sourceObj[mapEntry[sourceMapIndex]] === undefined) { + return targetObj; + } + + if (typeof mapEntry[targetMapIndex] === 'function') { + const result = mapEntry[targetMapIndex](sourceObj[mapEntry[sourceMapIndex]]); + Object.assign(targetObj, result); + } else { + // eslint-disable-next-line no-param-reassign + targetObj[mapEntry[targetMapIndex]] = sourceObj[mapEntry[sourceMapIndex]]; + } + + return targetObj; + }, {}); +} + +/** + * Map the model object to a plain old object based on the model map. If the source + * object does not contain a key that is specified in the mapping array, that key + * will not be included in the resulting object. + * + * @param {object} model The model to convert to a POJO + * @param {array} modelMap Map mapping array that contains multiple arrays. The nested arrays + * containing key mappings between objects. + * Inner array format can be either ['modelPropertyKey', ['apiPropertyKey'] or + * ['modelPropertyKey', modelToApiFn()] + */ +export function map(model, modelMap) { + return mappingReducer(model, modelMap); +} diff --git a/packages/spacecat-shared-data-access/src/service/import-job/accessPatterns.js b/packages/spacecat-shared-data-access/src/service/import-job/accessPatterns.js index 5a591867..28203017 100644 --- a/packages/spacecat-shared-data-access/src/service/import-job/accessPatterns.js +++ b/packages/spacecat-shared-data-access/src/service/import-job/accessPatterns.js @@ -10,9 +10,11 @@ * governing permissions and limitations under the License. */ -import { isObject } from '@adobe/spacecat-shared-utils'; +import { isArray, isObject } from '@adobe/spacecat-shared-utils'; import { ImportJobDto } from '../../dto/import-job.js'; import { createImportJob } from '../../models/importer/import-job.js'; +import { getImportUrlsByJobId } from '../import-url/accessPatterns.js'; +import { ImportUrlStatus } from '../../models/importer/import-constants.js'; /** * Get all Import Jobs within a specific date range @@ -40,7 +42,7 @@ export const getImportJobsByDateRange = async (dynamoClient, config, log, startD }; /** - * Get Import Job by ID + * Get Import Job by ID. * @param {DynamoClient} dynamoClient * @param {Object} config * @param {Logger} log @@ -48,11 +50,50 @@ export const getImportJobsByDateRange = async (dynamoClient, config, log, startD * @returns {Promise | null} */ export const getImportJobByID = async (dynamoClient, config, log, id) => { - const item = await dynamoClient.getItem( + const jobEntity = await dynamoClient.getItem( config.tableNameImportJobs, { id }, ); - return item ? ImportJobDto.fromDynamoItem(item) : null; + + if (!jobEntity) { + return null; + } + + const jobModel = ImportJobDto.fromDynamoItem(jobEntity); + + const importUrls = await getImportUrlsByJobId(dynamoClient, config, log, id); + + if (isArray(importUrls)) { + jobModel.state.progress = importUrls.reduce((acc, importUrl) => { + // eslint-disable-next-line default-case + switch (importUrl.state.status) { + case ImportUrlStatus.PENDING: + acc.pending += 1; + break; + case ImportUrlStatus.REDIRECT: + acc.redirect += 1; + break; + case ImportUrlStatus.RUNNING: + acc.running += 1; + break; + case ImportUrlStatus.COMPLETE: + acc.completed += 1; + break; + case ImportUrlStatus.FAILED: + acc.failed += 1; + break; + } + return acc; + }, { + pending: 0, + redirect: 0, + running: 0, + completed: 0, + failed: 0, + }); + } + + return jobModel; }; /** diff --git a/packages/spacecat-shared-data-access/src/service/import-url/accessPatterns.js b/packages/spacecat-shared-data-access/src/service/import-url/accessPatterns.js index d5eddf7a..9b33756c 100644 --- a/packages/spacecat-shared-data-access/src/service/import-url/accessPatterns.js +++ b/packages/spacecat-shared-data-access/src/service/import-url/accessPatterns.js @@ -64,7 +64,7 @@ export const updateImportUrl = async (dynamoClient, config, log, importUrl) => { ); if (!isObject(existingImportUrl)) { - throw new Error(`Import Url with ID:${importUrl.getId()} does not exist`); + throw new Error(`Import Url with ID: ${importUrl.getId()} does not exist`); } await dynamoClient.putItem(config.tableNameImportUrls, ImportUrlDto.toDynamoItem(importUrl)); @@ -103,7 +103,7 @@ export const getImportUrlsByJobIdAndStatus = async (dynamoClient, config, log, j * @param {Object} config * @param {Logger} log * @param {string} jobId - * @returns {Promise} + * @returns {Promise} */ export const getImportUrlsByJobId = async (dynamoClient, config, log, jobId) => { const items = await dynamoClient.query({ @@ -114,5 +114,6 @@ export const getImportUrlsByJobId = async (dynamoClient, config, log, jobId) => ':jobId': jobId, }, }); - return items.map((item) => ImportUrlDto.fromDynamoItem(item)); + + return items ? items.map(ImportUrlDto.fromDynamoItem) : null; }; diff --git a/packages/spacecat-shared-data-access/test/it/db.test.js b/packages/spacecat-shared-data-access/test/it/db.test.js index 5e1dff02..d58f847a 100644 --- a/packages/spacecat-shared-data-access/test/it/db.test.js +++ b/packages/spacecat-shared-data-access/test/it/db.test.js @@ -25,9 +25,14 @@ import { configSchema } from '../../src/models/site/config.js'; import { AUDIT_TYPE_LHS_MOBILE } from '../../src/models/audit.js'; import generateSampleData from './generateSampleData.js'; -import { createSiteCandidate, SITE_CANDIDATE_SOURCES, SITE_CANDIDATE_STATUS } from '../../src/models/site-candidate.js'; +import { + createSiteCandidate, + SITE_CANDIDATE_SOURCES, + SITE_CANDIDATE_STATUS, +} from '../../src/models/site-candidate.js'; import { KEY_EVENT_TYPES } from '../../src/models/key-event.js'; import { ConfigurationDto } from '../../src/dto/configuration.js'; +import { ImportJobStatus, ImportOptions, ImportUrlStatus } from '../../src/index.js'; use(chaiAsPromised); @@ -110,10 +115,13 @@ const TEST_DA_CONFIG = { indexNameAllSitesByDeliveryType: 'spacecat-services-all-sites-by-delivery-type', indexNameAllLatestAuditScores: 'spacecat-services-all-latest-audit-scores', indexNameAllImportJobsByStatus: 'spacecat-services-all-import-jobs-by-status', + indexNameImportUrlsByJobIdAndStatus: 'spacecat-services-all-import-urls-by-job-id-and-status', + indexNameAllImportJobsByDateRange: 'spacecat-services-all-import-jobs-by-date-range', pkAllSites: 'ALL_SITES', pkAllOrganizations: 'ALL_ORGANIZATIONS', pkAllLatestAudits: 'ALL_LATEST_AUDITS', pkAllConfigurations: 'ALL_CONFIGURATIONS', + pkAllImportJobs: 'ALL_IMPORT_JOBS', }; describe('DynamoDB Integration Test', async () => { @@ -987,4 +995,154 @@ describe('DynamoDB Integration Test', async () => { const organizationAfterRemoval = await dataAccess.getOrganizationByID(organization.getId()); expect(organizationAfterRemoval).to.be.null; }); + + /** + * The following section is related to the Importer. + * It includes tests for the ImportJob and ImportUrl Data Access APIs. + * + * Before running the tests inject a ImportJob and ImportUrl into their respective tables. + * This is done such that each test could be executed individually without the need to run the + * entire suite. + */ + describe('Importer Tests', async () => { + const startTime = new Date().toISOString(); + + // helper + const createNewImportJob = async () => dataAccess.createNewImportJob({ + urls: ['https://example.com/cars', 'https://example.com/bikes'], + importQueueId: 'Q-123', + hashedApiKey: '1234', + baseURL: 'https://example.com/cars', + startTime, + status: ImportJobStatus.RUNNING, + initiatedBy: { + apiKeyName: 'K-123', + }, + options: { + [ImportOptions.ENABLE_JAVASCRIPT]: true, + }, + }); + + // helper + const createNewImportUrl = async (importJob) => dataAccess.createNewImportUrl({ + url: 'https://example.com/cars', + jobId: importJob.getId(), + status: ImportUrlStatus.PENDING, + }); + + describe('Import Job Tests', async () => { + it('Verify the creation of the import job.', async () => { + const job = await createNewImportJob(); + expect(job.getId()).to.be.a('string'); + expect(job.getCreatedAt()).to.be.a('string'); + }); + + it('Verify updateImportJob', async () => { + const job = await createNewImportJob(); + const newJob = { ...job }; + const newEndTime = new Date().toISOString(); + newJob.updateStatus(ImportJobStatus.COMPLETE); + newJob.updateEndTime(newEndTime); + newJob.updateDuration(1234); + newJob.updateUrlCount(100); + newJob.updateImportQueueId('Q-456'); + + const updatedJob = await dataAccess.updateImportJob(newJob); + + expect(updatedJob.getStatus()).to.be.equal(ImportJobStatus.COMPLETE); + expect(updatedJob.getEndTime()).to.equal(newEndTime); + expect(updatedJob.getDuration()).to.be.equal(1234); + expect(updatedJob.getUrlCount()).to.be.equal(100); + expect(updatedJob.getImportQueueId()).to.be.equal('Q-456'); + expect(updatedJob.getProgress()).to.deep.equal({ + running: 0, + failed: 0, + completed: 0, + pending: 0, + redirect: 0, + }); + expect(updatedJob.getOptions()).to.deep.equal({ + [ImportOptions.ENABLE_JAVASCRIPT]: true, + }); + }); + + it('Verify getImportJobsByStatus', async () => { + const job = await createNewImportJob(); + job.updateStatus(ImportJobStatus.FAILED); + await dataAccess.updateImportJob(job); + const result = await dataAccess.getImportJobsByStatus(ImportJobStatus.FAILED); + expect(result.length).to.be.greaterThan(0); + }); + + it('Verify getImportJobByID', async () => { + const job = await createNewImportJob(); + const jobEntry = await dataAccess.getImportJobByID(job.getId()); + expect(job.getId()).to.be.equal(jobEntry.getId()); + expect(job.getProgress()).to.deep.equal({ + running: 0, + failed: 0, + completed: 0, + pending: 0, + redirect: 0, + }); + }); + + it('Verify getImportJobsByDateRange', async () => { + const endDate = new Date().toISOString(); + const jobs = await dataAccess.getImportJobsByDateRange(startTime, endDate); + expect(jobs.length).to.be.greaterThan(0); + }); + }); + + describe('Import URL Tests', async () => { + it('Verify the creation of a new import url.', async () => { + const job = await createNewImportJob(); + const url = await createNewImportUrl(job); + + expect(url.getId()).to.be.a('string'); + expect(url.getJobId()).to.equal(job.getId()); + }); + + it('Verify getImportUrlById', async () => { + const job = await createNewImportJob(); + const url = await createNewImportUrl(job); + const urlRow = await dataAccess.getImportUrlById(url.getId()); + expect(urlRow.getId()).to.be.equal(url.getId()); + }); + + it('Verify getImportUrlsByJobId', async () => { + const job = await createNewImportJob(); + await createNewImportUrl(job); + const urlRow = await dataAccess.getImportUrlsByJobId(job.getId()); + expect(urlRow.length).to.be.greaterThan(0); + }); + + it('Verify getImportUrlsByJobIdAndStatus', async () => { + const job = await createNewImportJob(); + await createNewImportUrl(job); + + const urlRows = await dataAccess + .getImportUrlsByJobIdAndStatus(job.getId(), ImportUrlStatus.PENDING); + expect(urlRows.length).to.be.greaterThan(0); + }); + + it('Verify updateImportUrl', async () => { + const job = await createNewImportJob(); + const url = await createNewImportUrl(job); + + const newUrl = { ...url }; + newUrl.setStatus(ImportUrlStatus.COMPLETE); + newUrl.setReason('Just Because'); + newUrl.setPath('/path/to/file'); + newUrl.setFile('thefile.docx'); + + const updatedUrl = await dataAccess.updateImportUrl(newUrl); + + expect(updatedUrl.getStatus()).to.be.equal(ImportUrlStatus.COMPLETE); + expect(updatedUrl.getReason()).to.be.equal('Just Because'); + expect(updatedUrl.getPath()).to.be.equal('/path/to/file'); + expect(updatedUrl.getFile()).to.be.equal('thefile.docx'); + }); + }); + }); }); diff --git a/packages/spacecat-shared-data-access/test/unit/models/importer/import-job.test.js b/packages/spacecat-shared-data-access/test/unit/models/importer/import-job.test.js index 55e32de1..8d34cb9f 100644 --- a/packages/spacecat-shared-data-access/test/unit/models/importer/import-job.test.js +++ b/packages/spacecat-shared-data-access/test/unit/models/importer/import-job.test.js @@ -141,16 +141,6 @@ describe('ImportJob Model tests', () => { expect(importJob.getUrlCount()).to.equal(10); }); - it('updates success count of import job', () => { - importJob.updateSuccessCount(10); - expect(importJob.getSuccessCount()).to.equal(10); - }); - - it('updates failed count of import job', () => { - importJob.updateFailedCount(5); - expect(importJob.getFailedCount()).to.equal(5); - }); - it('updates import queue id of import job', () => { importJob.updateImportQueueId('123'); expect(importJob.getImportQueueId()).to.equal('123'); @@ -168,14 +158,6 @@ describe('ImportJob Model tests', () => { expect(() => importJob.updateDuration('invalid-duration')).to.throw('Invalid duration during update: invalid-duration'); }); - it('throws an error if success count is not a valid number during an update', () => { - expect(() => importJob.updateSuccessCount('invalid-count')).to.throw('Invalid success count during update: invalid-count'); - }); - - it('throws an error if failed count is not a valid number during an update', () => { - expect(() => importJob.updateFailedCount('invalid-count')).to.throw('Invalid failed count during update: invalid-count'); - }); - it('throws an error if url count is not a valid number during an update', () => { expect(() => importJob.updateUrlCount('invalid-count')).to.throw('Invalid url count during update: invalid-count'); }); @@ -201,5 +183,15 @@ describe('ImportJob Model tests', () => { it('retrieves the startTime of the import job', () => { expect(importJob.getStartTime()).to.equal('2024-05-29T14:26:00.000Z'); }); + + it('retrieves the progress of the job', () => { + expect(importJob.getProgress()).to.deep.equal({ + running: 0, + failed: 0, + completed: 0, + pending: 0, + redirect: 0, + }); + }); }); }); diff --git a/packages/spacecat-shared-data-access/test/unit/models/importer/import-model-mapper.test.js b/packages/spacecat-shared-data-access/test/unit/models/importer/import-model-mapper.test.js new file mode 100644 index 00000000..2d71319f --- /dev/null +++ b/packages/spacecat-shared-data-access/test/unit/models/importer/import-model-mapper.test.js @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +/* eslint-env mocha */ + +import { expect } from 'chai'; +import { map } from '../../../../src/models/importer/import-model-mapper.js'; + +describe('import-model-mapper', () => { + let model; + let modelMap; + + beforeEach(() => { + model = { + id: '123', + name: 'Test Import', + func: false, + }; + + modelMap = [ + ['id', 'identification'], + ['name', 'name'], + ['func', () => ({ func: true })], + ['nonExistent', 'nonExistent'], + ]; + }); + + it('should map a model to an API response object', () => { + const result = map(model, modelMap); + expect(result).to.deep.equal({ + identification: '123', + name: 'Test Import', + func: true, + }); + }); +}); diff --git a/packages/spacecat-shared-data-access/test/unit/service/import-job/index.test.js b/packages/spacecat-shared-data-access/test/unit/service/import-job/index.test.js index 1e39e4f5..42d24185 100644 --- a/packages/spacecat-shared-data-access/test/unit/service/import-job/index.test.js +++ b/packages/spacecat-shared-data-access/test/unit/service/import-job/index.test.js @@ -18,6 +18,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { importJobFunctions } from '../../../../src/service/import-job/index.js'; import { createImportJob } from '../../../../src/models/importer/import-job.js'; +import { ImportUrlStatus } from '../../../../src/index.js'; use(sinonChai); use(chaiAsPromised); @@ -66,10 +67,29 @@ describe('Import Job Tests', () => { userAgent: 'test-user-agent', }, }; + + const urls = []; + Object.values(ImportUrlStatus).forEach((status) => { + const mockImportUrl = { + id: `test-import-url-${status}`, + jobId: 'test-id', + status, + url: `https://www.test.com/${status}`, + }; + urls.push(mockImportUrl); + }); + mockDynamoClient.getItem.resolves(mockImportJob); + mockDynamoClient.query.resolves(urls); + const result = await exportedFunctions.getImportJobByID('test-id'); expect(result.state.id).to.equal('test-id'); + expect(result.state.progress.pending).to.equal(1); + expect(result.state.progress.redirect).to.equal(1); + expect(result.state.progress.running).to.equal(1); + expect(result.state.progress.completed).to.equal(1); + expect(result.state.progress.failed).to.equal(1); }); it('should return null if item is not found', async () => { diff --git a/packages/spacecat-shared-data-access/test/unit/service/import-url/index.test.js b/packages/spacecat-shared-data-access/test/unit/service/import-url/index.test.js index 39d0e335..53124646 100644 --- a/packages/spacecat-shared-data-access/test/unit/service/import-url/index.test.js +++ b/packages/spacecat-shared-data-access/test/unit/service/import-url/index.test.js @@ -18,6 +18,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { importUrlFunctions } from '../../../../src/service/import-url/index.js'; import { createImportUrl } from '../../../../src/models/importer/import-url.js'; +import { ImportJobStatus } from '../../../../src/index.js'; use(sinonChai); use(chaiAsPromised); @@ -31,6 +32,7 @@ describe('Import Url Tests', () => { let mockDynamoClient; let mockLog; let exportedFunctions; + let mockImportUrl; beforeEach(() => { mockDynamoClient = { @@ -42,35 +44,31 @@ describe('Import Url Tests', () => { log: sinon.stub(), }; exportedFunctions = importUrlFunctions(mockDynamoClient, TEST_DA_CONFIG, mockLog); + + mockImportUrl = { + id: 'test-url-id', + status: ImportJobStatus.RUNNING, + url: 'https://www.test.com', + jobId: 'test-job-id', + }; }); describe('getImportUrlByID', () => { it('should return an ImportUrlDto when an item is found', async () => { - const mockImportUrl = { - id: 'test-id', - status: 'RUNNING', - url: 'https://www.test.com', - jobId: 'test-job-id', - }; mockDynamoClient.getItem.resolves(mockImportUrl); - const result = await exportedFunctions.getImportUrlById('test-id'); - expect(result.state.id).to.equal('test-id'); + const result = await exportedFunctions.getImportUrlById('test-url-id'); + expect(result.state.id).to.equal('test-url-id'); }); it('should return null when an item is not found', async () => { mockDynamoClient.getItem.resolves(null); - const result = await exportedFunctions.getImportUrlById('test-id'); + const result = await exportedFunctions.getImportUrlById('test-url-id'); expect(result).to.be.null; }); }); describe('createImportUrl', () => { it('should create an ImportUrlDto with the correct status', async () => { - const mockImportUrl = { - id: 'test-id', - status: 'RUNNING', - url: 'https://www.test.com', - }; await exportedFunctions.createNewImportUrl(mockImportUrl); expect(mockDynamoClient.putItem.calledOnce).to.be.true; }); @@ -78,43 +76,26 @@ describe('Import Url Tests', () => { describe('updateImportUrl', () => { it('should update an existing importUrl with the correct status', async () => { - const mockImportUrl = { - id: 'test-id', - status: 'RUNNING', - url: 'https://www.test.com', - }; mockDynamoClient.getItem.resolves(mockImportUrl); - const importUrl = await exportedFunctions.getImportUrlById('test-id'); - importUrl.setStatus('COMPLETE'); + const importUrl = await exportedFunctions.getImportUrlById('test-url-id'); + importUrl.setStatus(ImportJobStatus.COMPLETE); const result = await exportedFunctions.updateImportUrl(importUrl); expect(result).to.be.not.null; expect(mockDynamoClient.putItem).to.have.been.calledOnce; - expect(result.getStatus()).to.equal('COMPLETE'); + expect(result.getStatus()).to.equal(ImportJobStatus.COMPLETE); }); it('should throw an error when the importUrl does not exist', async () => { - const mockImportUrl = { - id: 'test-id', - status: 'RUNNING', - url: 'https://www.test.com', - }; - const importUrl = createImportUrl(mockImportUrl); const result = exportedFunctions.updateImportUrl(importUrl); - await expect(result).to.be.rejectedWith('Import Url with ID:test-id does not exist'); + await expect(result).to.be.rejectedWith('Import Url with ID: test-url-id does not exist'); }); }); describe('getImportUrlsByJobIdAndStatus', () => { it('should return an array of ImportUrlDto when items are found', async () => { - const mockImportUrl = { - id: 'test-id', - status: 'RUNNING', - url: 'https://www.test.com', - jobId: 'test-job-id', - }; mockDynamoClient.query.resolves([mockImportUrl]); const result = await exportedFunctions.getImportUrlsByJobIdAndStatus('test-job-id', 'RUNNING'); expect(result.length).to.equal(1); @@ -124,12 +105,6 @@ describe('Import Url Tests', () => { describe('getImportUrlsByJobId', () => { it('should return an array of ImportUrl when items are found', async () => { - const mockImportUrl = { - id: 'test-url-id', - status: 'RUNNING', - url: 'https://www.test.com', - jobId: 'test-job-id', - }; mockDynamoClient.query.resolves([mockImportUrl]); const result = await exportedFunctions.getImportUrlsByJobId('test-url-id'); expect(result.length).to.equal(1);