Skip to content

Commit

Permalink
EW-771 working on code structure
Browse files Browse the repository at this point in the history
  • Loading branch information
psachmann committed Apr 10, 2024
1 parent 892fc07 commit 2054ce5
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -1,47 +1,152 @@
import { DeepMocked, createMock } from '@golevelup/ts-jest';
import AdmZip from 'adm-zip';
import { readFile } from 'node:fs/promises';
import { CommonCartridgeFileParser } from './common-cartridge-file-parser';
import { CommonCartridgeManifestParser } from './common-cartridge-manifest-parser';
import { CommonCartridgeManifestNotFoundException } from './utils/common-cartridge-manifest-not-found.exception';

describe('CommonCartridgeFileParser', () => {
let sut: CommonCartridgeFileParser;
let manifestParserMock: DeepMocked<CommonCartridgeManifestParser>;

const setupFile = async (hasManifest: boolean) => {
let file: Buffer;

if (hasManifest) {
const manifest = await readFile('./apps/server/src/modules/common-cartridge/testing/assets/v1.1.0/manifest.xml');
const archive = new AdmZip();

archive.addFile('imsmanifest.xml', manifest);

file = archive.toBuffer();
} else {
file = new AdmZip().toBuffer();
}

return file;
};

const setupParser = async (hasManifest: boolean) => {
const file = await setupFile(hasManifest);

return new CommonCartridgeFileParser(file);
};

beforeAll(async () => {
sut = await setupParser(true);
manifestParserMock = createMock<CommonCartridgeManifestParser>();

jest.spyOn(CommonCartridgeManifestParser.prototype, 'getSchema').mockReturnValue('IMS Common Cartridge');
Reflect.set(sut, 'manifestParser', manifestParserMock);
});

beforeEach(() => {
jest.clearAllMocks();
});

describe('constructor', () => {
describe('when manifest file is found', () => {
const setup = (manifestName: string) => {
const archive = new AdmZip();
const setup = () => setupFile(true);

archive.addFile(manifestName, Buffer.from('<manifest></manifest>'));
it('should create instance', async () => {
const file = await setup();

const file = archive.toBuffer();
expect(() => new CommonCartridgeFileParser(file)).not.toThrow();
});
});

return { file };
};
describe('when manifest file is not found', () => {
const setup = () => setupFile(false);

it('should use imsmanifest.xml as manifest', () => {
const { file } = setup('imsmanifest.xml');
const parser = new CommonCartridgeFileParser(file);
it('should throw CommonCartridgeManifestNotFoundException', async () => {
const file = await setup();

expect(parser.manifest).toBeDefined();
expect(() => new CommonCartridgeFileParser(file)).toThrow(CommonCartridgeManifestNotFoundException);
});
});
});

describe('getSchema', () => {
describe('when schema is found', () => {
const setup = () => setupParser(true);

it('should use manifest.xml as manifest', () => {
const { file } = setup('manifest.xml');
const parser = new CommonCartridgeFileParser(file);
it('should return schema', async () => {
const parser = await setup();

expect(parser.manifest).toBeDefined();
const schema = parser.getSchema();

expect(schema).toEqual('IMS Common Cartridge');
});
});

describe('when manifest file is not found', () => {
const setup = () => {
const archive = new AdmZip();
const file = archive.toBuffer();
describe('when schema is not found', () => {
const setup = () => setupParser(true);

it('should return undefined', async () => {
const parser = await setup();

const schema = parser.getSchema();

expect(schema).toBeUndefined();
});
});
});

return { file };
};
describe('getVersion', () => {
describe('when version is found', () => {
const setup = () => setupParser(true);

it('should throw', () => {
const { file } = setup();
it('should return version', async () => {
const parser = await setup();

expect(() => new CommonCartridgeFileParser(file)).toThrow('Manifest file not found');
const version = parser.getVersion();

expect(version).toEqual('1.1.0');
});
});

describe('when version is not found', () => {
const setup = () => setupParser(true);

it('should return undefined', async () => {
const parser = await setup();

const version = parser.getVersion();

expect(version).toBeUndefined();
});
});
});

describe('getTitle', () => {
describe('when title is found', () => {
const setup = () => setupParser(true);

it('should return title', async () => {
const parser = await setup();

const title = parser.getTitle();

expect(title).toEqual('Common Cartridge Manifest');
});
});

describe('when title is not found', () => {
const setup = () => setupParser(true);

it('should return undefined', async () => {
const parser = await setup();

const title = parser.getTitle();

expect(title).toBeUndefined();
});
});
});

describe('getOrganizations', () => {});

describe('getResource', () => {});

describe('getResourceAsString', () => {});
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import AdmZip from 'adm-zip';
import { JSDOM } from 'jsdom';
import {
DEFAULT_FILE_PARSER_OPTIONS,
OrganizationProps,
ResourceProps,
ResourceType,
} from './common-cartridge-import.types';
import { DEFAULT_FILE_PARSER_OPTIONS, OrganizationProps, ResourceProps } from './common-cartridge-import.types';
import { CommonCartridgeManifestParser } from './common-cartridge-manifest-parser';
import { CommonCartridgeResourceFactory } from './common-cartridge-resource-factory';
import { CommonCartridgeImportUtils } from './utils/common-cartridge-import-utils';
import { CommonCartridgeManifestNotFoundException } from './utils/common-cartridge-manifest-not-found.exception';
import { CommonCartridgeResourceNotFoundException } from './utils/common-cartridge-resource-not-found.exception';

Expand All @@ -15,32 +11,44 @@ export class CommonCartridgeFileParser {

private readonly archive: AdmZip;

private readonly resourceFactory: CommonCartridgeResourceFactory;

public constructor(file: Buffer, private readonly options = DEFAULT_FILE_PARSER_OPTIONS) {
this.archive = new AdmZip(file);
this.manifestParser = new CommonCartridgeManifestParser(this.getManifestFileAsString(), this.options);
this.resourceFactory = new CommonCartridgeResourceFactory(this.archive);
}

public get manifest(): CommonCartridgeManifestParser {
return this.manifestParser;
public getSchema(): string | undefined {
const schema = this.manifestParser.getSchema();

return schema;
}

public getResource(organization: OrganizationProps): ResourceProps | null {
public getVersion(): string | undefined {
const version = this.manifestParser.getVersion();

return version;
}

public getTitle(): string | undefined {
const title = this.manifestParser.getTitle();

return title;
}

public getOrganizations(): OrganizationProps[] {
const organizations = this.manifestParser.getOrganizations();

return organizations;
}

public getResource(organization: OrganizationProps): ResourceProps | undefined {
this.checkOrganization(organization);

if (organization.resourceType.startsWith('imswl_')) {
const resourceString = this.archive.readAsText(organization.resourcePath);
const resourceXml = new JSDOM(resourceString, { contentType: 'text/xml' }).window.document;
const title = resourceXml.querySelector('webLink > title')?.textContent || '';
const url = resourceXml.querySelector('webLink > url')?.getAttribute('href') || '';

return {
type: ResourceType.WEB_LINK,
title,
url,
};
}
const resource = this.resourceFactory.create(organization);

return null;
return resource;
}

public getResourceAsString(organization: OrganizationProps): string {
Expand All @@ -51,12 +59,11 @@ export class CommonCartridgeFileParser {
return resource;
}

private getManifestFileAsString(): string | never {
// imsmanifest.xml is the standard name, but manifest.xml is also valid until v1.3
const manifest = this.archive.getEntry('imsmanifest.xml') || this.archive.getEntry('manifest.xml');
private getManifestFileAsString(): string {
const manifest = CommonCartridgeImportUtils.getManifestFileAsString(this.archive);

if (manifest) {
return this.archive.readAsText(manifest);
return manifest;
}

throw new CommonCartridgeManifestNotFoundException();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum CommonCartridgeResourceTypeV1P1 {
UNKNOWN = 'unknown',
WEB_CONTENT = 'webcontent',
WEB_LINK = 'imswl_xmlv1p1',
}

export enum CommonCartridgeResourceTypeV1P3 {
WEB_LINK = 'imswl_xmlv1p3',
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CommonCartridgeResourceTypeV1P1 } from './common-cartridge-import.enums';

export type CommonCartridgeFileParserOptions = {
maxSearchDepth: number;
pathSeparator: string;
Expand All @@ -8,36 +10,26 @@ export const DEFAULT_FILE_PARSER_OPTIONS: CommonCartridgeFileParserOptions = {
pathSeparator: '/',
};

export enum OrganizationType {
UNKNOWN = 'unknown',
TITLE = 'title',
WEB_LINK = 'weblink',
}

export enum ResourceType {
UNKNOWN = 'unknown',
TITLE = 'title',
WEB_CONTENT = 'webcontent',
WEB_LINK = 'weblink',
}

export type OrganizationProps = {
path: string;
pathDepth: number;
identifier: string;
identifierRef?: string;
title: string;
isResource: boolean;
isInlined: boolean;
resourcePath: string;
resourceType: string;
};

export type TitleResourceProps = { type: ResourceType.TITLE; title: string };

export type WebContentResourceProps = { type: ResourceType.WEB_CONTENT; title: string; html: string };
export type WebContentResourceProps = {
type: CommonCartridgeResourceTypeV1P1.WEB_CONTENT;
title: string;
html: string;
};

export type WebLinkResourceProps = { type: ResourceType.WEB_LINK; title: string; url: string };
export type WebLinkResourceProps = { type: CommonCartridgeResourceTypeV1P1.WEB_LINK; title: string; url: string };

export type UnknownResourceProps = { type: ResourceType.UNKNOWN };
export type UnknownResourceProps = { type: CommonCartridgeResourceTypeV1P1.UNKNOWN };

export type ResourceProps = TitleResourceProps | WebContentResourceProps | WebLinkResourceProps | UnknownResourceProps;
export type ResourceProps = WebContentResourceProps | WebLinkResourceProps | UnknownResourceProps;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import AdmZip from 'adm-zip';
import { JSDOM } from 'jsdom';
import { CommonCartridgeResourceTypeV1P1, CommonCartridgeResourceTypeV1P3 } from './common-cartridge-import.enums';
import { OrganizationProps, ResourceProps, WebLinkResourceProps } from './common-cartridge-import.types';

export class CommonCartridgeResourceFactory {
constructor(private readonly archive: AdmZip) {}

public create(organization: OrganizationProps): ResourceProps | undefined {
if (!this.isValidOrganization(organization)) {
return undefined;
}

const content = this.archive.readAsText(organization.resourcePath);

switch (organization.resourceType) {
case CommonCartridgeResourceTypeV1P1.WEB_LINK:
case CommonCartridgeResourceTypeV1P3.WEB_LINK:
return this.createWebLinkResource(content);
default:
return undefined;
}
}

private isValidOrganization(organization: OrganizationProps): boolean {
const { isResource, isInlined, resourcePath } = organization;
const isValidOrganization = isResource && !isInlined && resourcePath !== '';

return isValidOrganization;
}

private createWebLinkResource(content: string): WebLinkResourceProps {
// TODO: Can throw an error if the content is not a valid XML
const resource = new JSDOM(content, { contentType: 'text/xml' }).window.document;
const title = resource.querySelector('webLink > title')?.textContent || '';
const url = resource.querySelector('webLink > url')?.getAttribute('href') || '';

return {
type: CommonCartridgeResourceTypeV1P1.WEB_LINK,
title,
url,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export class CommonCartridgeOrganizationVisitor {
const isResource = identifierRef !== '';
const resourcePath = isResource ? this.getResourcePath(identifierRef) : '';
const resourceType = isResource ? this.getResourceType(identifierRef) : '';
const isInlined = isResource && !resourcePath;

return {
path: element.path,
Expand All @@ -89,6 +90,7 @@ export class CommonCartridgeOrganizationVisitor {
identifierRef,
title,
isResource,
isInlined,
resourcePath,
resourceType,
};
Expand Down
Loading

0 comments on commit 2054ce5

Please sign in to comment.