-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(scenarios): create input zip archive from files
- Loading branch information
Showing
8 changed files
with
261 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
api/apps/api/src/modules/scenarios/input-files/input-files-archiver.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { Test } from '@nestjs/testing'; | ||
import { Injectable } from '@nestjs/common'; | ||
import { PromiseType } from 'utility-types'; | ||
import { Either, isLeft } from 'fp-ts/Either'; | ||
import * as unzipper from 'unzipper'; | ||
|
||
import { InputFilesArchiverService } from './input-files-archiver.service'; | ||
import { IoSettings } from './input-params/io-settings'; | ||
import { InputFilesService } from './input-files.service'; | ||
import { Writable } from 'stream'; | ||
|
||
let fixtures: PromiseType<ReturnType<typeof getFixtures>>; | ||
|
||
beforeEach(async () => { | ||
fixtures = await getFixtures(); | ||
}); | ||
|
||
describe(`when all input files are available`, () => { | ||
beforeEach(() => { | ||
fixtures.GivenInputDirectoryIsDefined(); | ||
fixtures.GivenFilesContentIsAvailable(); | ||
}); | ||
|
||
it(`should include them in archive`, async () => { | ||
const archive = await fixtures.sut.archive(fixtures.scenarioId); | ||
await fixtures.ThenArchiveContainsInputFiles(archive); | ||
}); | ||
}); | ||
|
||
const getFixtures = async () => { | ||
const app = await Test.createTestingModule({ | ||
providers: [ | ||
InputFilesArchiverService, | ||
{ | ||
provide: InputFilesService, | ||
useClass: FakeInputFiles, | ||
}, | ||
], | ||
}).compile(); | ||
const inputFileService: FakeInputFiles = app.get(InputFilesService); | ||
const scenarioId = `scenario-id`; | ||
const inputDirectoryName = 'input-directory'; | ||
const specFileName = `spec-dat-file`; | ||
const boundFileName = `bound-dat-file`; | ||
const puvsprFileName = `puvspr-dat-file`; | ||
const puFileName = `pu-dat-file`; | ||
|
||
return { | ||
sut: app.get(InputFilesArchiverService), | ||
scenarioId, | ||
GivenInputDirectoryIsDefined: () => { | ||
inputFileService.settingsMock.mockImplementationOnce(() => ({ | ||
INPUTDIR: inputDirectoryName, | ||
SPECNAME: specFileName, | ||
BOUNDNAME: boundFileName, | ||
PUVSPRNAME: puvsprFileName, | ||
PUNAME: puFileName, | ||
})); | ||
}, | ||
GivenFilesContentIsAvailable: () => { | ||
inputFileService.inputParamsMock.mockImplementationOnce( | ||
async () => `input.dat content`, | ||
); | ||
inputFileService.specMock.mockImplementationOnce( | ||
async () => `spec.dat content`, | ||
); | ||
inputFileService.boundMock.mockImplementationOnce( | ||
async () => `bound.dat content`, | ||
); | ||
inputFileService.puvsprMock.mockImplementationOnce( | ||
async () => `puvspr.dat content`, | ||
); | ||
inputFileService.puMock.mockImplementationOnce( | ||
(_: string, writable: Writable) => { | ||
'costsurface'.split('').forEach((chunk) => { | ||
setTimeout(() => { | ||
writable.write(chunk); | ||
if (chunk === 'e') { | ||
writable.end(); | ||
} | ||
}, 10); | ||
}); | ||
}, | ||
); | ||
}, | ||
ThenArchiveContainsInputFiles: async (archive: Either<any, Buffer>) => { | ||
if (isLeft(archive)) { | ||
expect(archive.left).toBeUndefined(); | ||
return; | ||
} | ||
const directory = await unzipper.Open.buffer(archive.right); | ||
|
||
const inputDat = directory.files.find((e) => e.path === `input.dat`); | ||
expect(inputDat).toBeDefined(); | ||
expect((await inputDat!.buffer?.()).toString()).toEqual( | ||
`input.dat content`, | ||
); | ||
expect(inputFileService.inputParamsMock).toHaveBeenCalledWith(scenarioId); | ||
|
||
const specDat = directory.files.find( | ||
(e) => e.path === `${inputDirectoryName}/${specFileName}`, | ||
); | ||
expect(specDat).toBeDefined(); | ||
expect((await specDat!.buffer?.()).toString()).toEqual( | ||
`spec.dat content`, | ||
); | ||
expect(inputFileService.specMock).toHaveBeenCalledWith(scenarioId); | ||
|
||
const boundDat = directory.files.find( | ||
(e) => e.path === `${inputDirectoryName}/${boundFileName}`, | ||
); | ||
expect(boundDat).toBeDefined(); | ||
expect((await boundDat!.buffer?.()).toString()).toEqual( | ||
`bound.dat content`, | ||
); | ||
expect(inputFileService.boundMock).toHaveBeenCalledWith(scenarioId); | ||
|
||
const puvSprDat = directory.files.find( | ||
(e) => e.path === `${inputDirectoryName}/${puvsprFileName}`, | ||
); | ||
expect(puvSprDat).toBeDefined(); | ||
expect((await puvSprDat!.buffer?.()).toString()).toEqual( | ||
`puvspr.dat content`, | ||
); | ||
expect(inputFileService.puvsprMock).toHaveBeenCalledWith(scenarioId); | ||
|
||
const puDat = directory.files.find( | ||
(e) => e.path === `${inputDirectoryName}/${puFileName}`, | ||
); | ||
expect(puDat).toBeDefined(); | ||
expect((await puDat!.buffer?.()).toString()).toEqual(`costsurface`); | ||
expect(inputFileService.puMock).toHaveBeenCalledWith( | ||
scenarioId, | ||
expect.anything(), | ||
); | ||
}, | ||
}; | ||
}; | ||
|
||
@Injectable() | ||
class FakeInputFiles { | ||
settingsMock = jest.fn<Partial<IoSettings>, []>(); | ||
inputParamsMock = jest.fn(); | ||
specMock = jest.fn(); | ||
boundMock = jest.fn(); | ||
puvsprMock = jest.fn(); | ||
puMock = jest.fn(); | ||
|
||
getSettings() { | ||
return this.settingsMock(); | ||
} | ||
|
||
getInputParameterFile(scenarioId: string) { | ||
return this.inputParamsMock(scenarioId); | ||
} | ||
|
||
getSpecDatContent(scenarioId: string) { | ||
return this.specMock(scenarioId); | ||
} | ||
|
||
getBoundDatContent(scenarioId: string) { | ||
return this.boundMock(scenarioId); | ||
} | ||
|
||
getPuvsprDatContent(scenarioId: string) { | ||
return this.puvsprMock(scenarioId); | ||
} | ||
|
||
readCostSurface(scenarioId: string, writeStream: Writable) { | ||
return this.puMock(scenarioId, writeStream); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
api/apps/api/src/modules/scenarios/input-files/input-files-archiver.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { Either, right } from 'fp-ts/Either'; | ||
import * as archiver from 'archiver'; | ||
|
||
import { InputFilesService, InputZipFailure } from './input-files.service'; | ||
import { PassThrough } from 'stream'; | ||
|
||
@Injectable() | ||
export class InputFilesArchiverService { | ||
constructor(private readonly inputFiles: InputFilesService) {} | ||
|
||
async archive(scenarioId: string): Promise<Either<InputZipFailure, Buffer>> { | ||
const archive = archiver('zip', { | ||
zlib: { level: 9 }, | ||
}); | ||
|
||
const settings = await this.inputFiles.getSettings(); | ||
const inputDirectory = settings.INPUTDIR; | ||
|
||
const inputDatContent = this.inputFiles.getInputParameterFile(scenarioId); | ||
const specDatContent = this.inputFiles.getSpecDatContent(scenarioId); | ||
const boundDatContent = this.inputFiles.getBoundDatContent(scenarioId); | ||
const puvsprDatContent = this.inputFiles.getPuvsprDatContent(scenarioId); | ||
const costSurfaceStreamContent = new PassThrough(); | ||
|
||
await this.inputFiles.readCostSurface(scenarioId, costSurfaceStreamContent); | ||
|
||
archive.append(costSurfaceStreamContent, { | ||
name: `${inputDirectory}/${settings.PUNAME}`, | ||
}); | ||
archive.append(await inputDatContent, { | ||
name: `input.dat`, | ||
}); | ||
archive.append(await specDatContent, { | ||
name: `${inputDirectory}/${settings.SPECNAME}`, | ||
}); | ||
archive.append(await boundDatContent, { | ||
name: `${inputDirectory}/${settings.BOUNDNAME}`, | ||
}); | ||
archive.append(await puvsprDatContent, { | ||
name: `${inputDirectory}/${settings.PUVSPRNAME}`, | ||
}); | ||
|
||
return new Promise((resolve, reject) => { | ||
const buffers: Buffer[] = []; | ||
archive.on('data', (chunk) => { | ||
buffers.push(chunk); | ||
}); | ||
archive.on('finish', () => { | ||
resolve(right(Buffer.concat(buffers))); | ||
}); | ||
archive.on('error', function (err) { | ||
reject(err); | ||
}); | ||
archive.finalize(); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters