From 94df15d82569fb6454e7a7bd3dd3834dccf9a9a9 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Mon, 15 Apr 2019 08:31:18 -0700 Subject: [PATCH] Added tests for project service --- package-lock.json | 20 ++++-- src/services/projectService.test.ts | 102 ++++++++++++++++++++++++++-- src/services/projectService.ts | 39 +++++++++-- 3 files changed, 143 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index d633f4d85a..7a5d34e053 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7282,7 +7282,8 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true }, "concat-map": { "version": "0.0.1", @@ -7293,7 +7294,8 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7423,6 +7425,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7552,7 +7555,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7650,7 +7654,8 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -7686,6 +7691,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7748,12 +7754,14 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "yallist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "optional": true } } }, diff --git a/src/services/projectService.test.ts b/src/services/projectService.test.ts index f610e750a3..7faa1fd180 100644 --- a/src/services/projectService.test.ts +++ b/src/services/projectService.test.ts @@ -2,11 +2,13 @@ import _ from "lodash"; import ProjectService, { IProjectService } from "./projectService"; import MockFactory from "../common/mockFactory"; import { StorageProviderFactory } from "../providers/storage/storageProviderFactory"; -import { IProject, IExportFormat, ISecurityToken, AssetState } from "../models/applicationState"; +import { IProject, IExportFormat, ISecurityToken, + AssetState, IAsset, IAssetMetadata } from "../models/applicationState"; import { constants } from "../common/constants"; import { ExportProviderFactory } from "../providers/export/exportProviderFactory"; import { generateKey } from "../common/crypto"; import { encryptProject } from "../common/utils"; +import { AssetService } from "./assetService"; describe("Project Service", () => { let projectSerivce: IProjectService = null; @@ -146,15 +148,105 @@ describe("Project Service", () => { expect(projectSerivce.isDuplicate(testProject, projectList)).toEqual(true); }); - it("deletes all asset metadata files when project is deleted", async () => { - const assets = MockFactory.createTestAssets(10); + function populateProjectAssets(project?: IProject, assetCount = 10) { + if (!project) { + project = MockFactory.createTestProject(); + } + const assets = MockFactory.createTestAssets(assetCount); assets.forEach((asset) => { asset.state = AssetState.Tagged; }); - testProject.assets = _.keyBy(assets, (asset) => asset.id); + project.assets = _.keyBy(assets, (asset) => asset.id); + return project; + } + + it("deletes all asset metadata files when project is deleted", async () => { + const assetCount = 10; + populateProjectAssets(testProject); await projectSerivce.delete(testProject); - expect(storageProviderMock.deleteFile.mock.calls).toHaveLength(assets.length + 1); + expect(storageProviderMock.deleteFile.mock.calls).toHaveLength(assetCount + 1); + }); + + it("Deletes tag from all assets within project", async () => { + const tag1 = "tag1"; + const tag2 = "tag2"; + const region = MockFactory.createTestRegion(undefined, [tag1, tag2]); + const asset: IAsset = { + ...MockFactory.createTestAsset("1"), + state: AssetState.Tagged, + }; + const assetMetadata = MockFactory.createTestAssetMetadata(asset, [region]); + AssetService.prototype.getAssetMetadata = jest.fn((asset: IAsset) => Promise.resolve(assetMetadata)); + + const saveMetadata = jest.fn(); + AssetService.prototype.save = saveMetadata; + + const expectedAssetMetadata: IAssetMetadata = { + ...MockFactory.createTestAssetMetadata( + asset, + [ + { + ...region, + tags: [tag2], + }, + ], + ), + + }; + const project = populateProjectAssets(); + await projectSerivce.deleteTag(project, tag1, assetMetadata); + expect(saveMetadata).toBeCalledWith(expectedAssetMetadata); + }); + + it("Deletes any empty regions after deleting only tag from region", async () => { + const tag1 = "tag1"; + const region = MockFactory.createTestRegion(undefined, [tag1]); + const asset: IAsset = { + ...MockFactory.createTestAsset("1"), + state: AssetState.Tagged, + }; + const assetMetadata = MockFactory.createTestAssetMetadata(asset, [region]); + AssetService.prototype.getAssetMetadata = jest.fn((asset: IAsset) => Promise.resolve(assetMetadata)); + + const saveMetadata = jest.fn(); + AssetService.prototype.save = saveMetadata; + + const expectedAssetMetadata: IAssetMetadata = MockFactory.createTestAssetMetadata(asset, []); + const project = populateProjectAssets(); + await projectSerivce.deleteTag(project, tag1, assetMetadata); + expect(saveMetadata).toBeCalledWith(expectedAssetMetadata); + }); + + it("Updates renamed tag within all assets of project", async () => { + const tag1 = "tag1"; + const newTag = "tag2"; + const region = MockFactory.createTestRegion(undefined, [tag1]); + const asset: IAsset = { + ...MockFactory.createTestAsset("1"), + state: AssetState.Tagged, + }; + const assetMetadata = MockFactory.createTestAssetMetadata(asset, [region]); + AssetService.prototype.getAssetMetadata = jest.fn((asset: IAsset) => Promise.resolve(assetMetadata)); + + const saveMetadata = jest.fn(); + AssetService.prototype.save = saveMetadata; + + const expectedAssetMetadata: IAssetMetadata = { + ...MockFactory.createTestAssetMetadata( + asset, + [ + { + ...region, + tags: [newTag], + }, + ], + ), + + }; + const project = populateProjectAssets(); + await projectSerivce.renameTag(project, tag1, newTag, assetMetadata); + expect(saveMetadata).toBeCalledWith(expectedAssetMetadata); }); }); diff --git a/src/services/projectService.ts b/src/services/projectService.ts index feb1b6e325..3fc0cf01ad 100644 --- a/src/services/projectService.ts +++ b/src/services/projectService.ts @@ -20,8 +20,10 @@ export interface IProjectService { save(project: IProject, securityToken: ISecurityToken): Promise; delete(project: IProject): Promise; isDuplicate(project: IProject, projectList: IProject[]): boolean; - deleteTag(project: IProject, tagName: string, currentAsset: IAssetMetadata): Promise; - renameTag(project: IProject, tagName: string, newTagName: string, currentAsset: IAssetMetadata): Promise; + deleteTag(project: IProject, tagName: string, + currentAsset: IAssetMetadata): Promise; + renameTag(project: IProject, tagName: string, newTagName: string, + currentAsset: IAssetMetadata): Promise; } /** @@ -111,8 +113,9 @@ export default class ProjectService implements IProjectService { * @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid * needing to reload the asset in the editor page */ - public async deleteTag(project: IProject, tagName: string, currentAsset: IAssetMetadata): Promise { - const transformer = (tags) => tags.filter((t) => t!== tagName); + public async deleteTag(project: IProject, tagName: string, + currentAsset: IAssetMetadata): Promise { + const transformer = (tags) => tags.filter((t) => t !== tagName); return await this.updateProjectTags(project, tagName, currentAsset, transformer); } @@ -123,7 +126,8 @@ export default class ProjectService implements IProjectService { * @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid * needing to reload the asset in the editor page */ - public async renameTag(project: IProject, tagName: string, newTagName: string, currentAsset: IAssetMetadata): Promise { + public async renameTag(project: IProject, tagName: string, newTagName: string, + currentAsset: IAssetMetadata): Promise { const transformer = (tags) => tags.map((t) => (t === tagName) ? newTagName : t); return await this.updateProjectTags(project, tagName, currentAsset, transformer); } @@ -136,9 +140,27 @@ export default class ProjectService implements IProjectService { * needing to reload the asset in the editor page * @param transformer Function that accepts array of tags from a region and returns a modified array of tags */ - private async updateProjectTags(project: IProject, tagName: string, currentAsset: IAssetMetadata, transformer: (tags: string[]) => string[]) { + private async updateProjectTags(project: IProject, tagName: string, currentAsset: IAssetMetadata, + transformer: (tags: string[]) => string[]): Promise { const assetService = new AssetService(project); const assetKeys = Object.keys(project.assets); + // Loop over assets and update if necessary + for (const assetKey of assetKeys) { + const asset = project.assets[assetKey]; + if (asset.state !== AssetState.Tagged) { + return; + } + const assetMetadata = await assetService.getAssetMetadata(asset); + const updatedAssetMetadata = this.updateTagInAssetMetadata(assetMetadata, tagName, transformer); + if (updatedAssetMetadata) { + await assetService.save(updatedAssetMetadata); + } + } + /* + TODO: Replace with async + + For some reason in tests, the `forEachAsync` is not recognized as a function + await assetKeys.forEachAsync(async (assetKey) => { const asset = project.assets[assetKey]; if (asset.state !== AssetState.Tagged) { @@ -150,6 +172,8 @@ export default class ProjectService implements IProjectService { await assetService.save(updatedAssetMetadata); } }); + + */ return this.updateTagInAssetMetadata(currentAsset, tagName, transformer); } @@ -160,7 +184,8 @@ export default class ProjectService implements IProjectService { * @param transformer Function that accepts array of tags from a region and returns a modified array of tags * @returns Modified asset metadata object or null if object does not need to be modified */ - private updateTagInAssetMetadata(assetMetadata: IAssetMetadata, tagName: string, transformer: (tags: string[]) => string[]) { + private updateTagInAssetMetadata(assetMetadata: IAssetMetadata, tagName: string, + transformer: (tags: string[]) => string[]): IAssetMetadata { let foundTag = false; for (const region of assetMetadata.regions) { if (region.tags.find((t) => t === tagName)) {