Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions src/services/__tests__/import-manifest-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import { processManifestTemplate } from 'services/import-manifest-service';
import { loadFile } from 'utils/file-system';
import type { ListrTaskWrapper } from 'listr2';

jest.mock('utils/file-system');
import { execute } from 'services/api-service';
import { compressFilesToZip, readZipFileAsBuffer } from 'services/files-service';
import * as importManifestService from 'services/import-manifest-service';
import { processManifestTemplate, uploadManifestTsk } from 'services/import-manifest-service';
import type { ImportCommandTasksContext } from 'types/commands/manifest-import';
import { loadFile, saveToFile, unlink } from 'utils/file-system';

jest.mock('services/api-service', () => ({
execute: jest.fn().mockResolvedValue({}),
}));

jest.mock('services/files-service', () => ({
compressFilesToZip: jest.fn(),
readZipFileAsBuffer: jest.fn(),
}));

jest.mock('utils/file-system', () => ({
loadFile: jest.fn(),
saveToFile: jest.fn(),
unlink: jest.fn(),
}));

describe('processManifestTemplate', () => {
const mockLoadFile = loadFile as jest.MockedFunction<typeof loadFile>;
Expand Down Expand Up @@ -33,3 +52,55 @@ describe('processManifestTemplate', () => {
await expect(processManifestTemplate(mockPath)).rejects.toThrow(/Missing variable/);
});
});

describe('uploadManifestTsk', () => {
const mockCompressFilesToZip = compressFilesToZip as jest.MockedFunction<typeof compressFilesToZip>;
const mockReadZipFileAsBuffer = readZipFileAsBuffer as jest.MockedFunction<typeof readZipFileAsBuffer>;
const mockSaveToFile = saveToFile as jest.MockedFunction<typeof saveToFile>;
const mockUnlink = unlink as jest.MockedFunction<typeof unlink>;
const mockExecute = execute as jest.MockedFunction<typeof execute>;

const zipPath = '/tmp/mapps-compress-test.zip';
// biome-ignore lint/suspicious/noExplicitAny: Listr renderer generic is unused by uploadManifestTsk
const taskStub = { output: '', title: '' } as unknown as ListrTaskWrapper<ImportCommandTasksContext, any>;

beforeEach(() => {
jest.spyOn(importManifestService, 'processManifestTemplate').mockResolvedValue({ name: 'test-app' });
mockCompressFilesToZip.mockResolvedValue(zipPath);
mockReadZipFileAsBuffer.mockReturnValue(Buffer.from('zip-bytes'));
mockSaveToFile.mockResolvedValue(undefined);
mockUnlink.mockResolvedValue(undefined);
mockExecute.mockResolvedValue({} as Awaited<ReturnType<typeof execute>>);
});

afterEach(() => {
jest.restoreAllMocks();
});

it('unlinks the temp zip and processed manifest after a successful upload', async () => {
const ctx = {
manifestFilePath: '/project/manifest.json',
allowMissingVariables: false,
appId: 1 as const,
};

await uploadManifestTsk(ctx, taskStub);

expect(mockUnlink).toHaveBeenCalledWith(zipPath);
expect(mockUnlink).toHaveBeenCalledWith('/project/manifest.json.processed');
});

it('unlinks the temp zip and processed manifest when upload fails', async () => {
mockExecute.mockRejectedValueOnce(new Error('upload failed'));

const ctx = {
manifestFilePath: '/project/manifest.json',
allowMissingVariables: false,
};

await expect(uploadManifestTsk(ctx, taskStub)).rejects.toThrow('upload failed');

expect(mockUnlink).toHaveBeenCalledWith(zipPath);
expect(mockUnlink).toHaveBeenCalledWith('/project/manifest.json.processed');
});
});
3 changes: 2 additions & 1 deletion src/services/files-service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
Expand Down Expand Up @@ -42,7 +43,7 @@ export const decompressZipBufferToFiles = async (buffer: Buffer, outputDir: stri
};

export const compressFilesToZip = async (files: { path: string; replaceName?: string }[]): Promise<string> => {
const tempZipPath = 'temp.zip';
const tempZipPath = path.join(os.tmpdir(), `mapps-compress-${randomUUID()}.zip`);
const output = fs.createWriteStream(tempZipPath);
const archive = archiver('zip', { zlib: { level: 9 } });

Expand Down
10 changes: 9 additions & 1 deletion src/services/import-manifest-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,26 @@ export const uploadManifestTsk = async (
ctx: ImportCommandTasksContext,
task: ListrTaskWrapper<ImportCommandTasksContext, any>,
) => {
let zipFilePath: string | undefined;
try {
const processedManifest = await processManifestTemplate(ctx.manifestFilePath, ctx.allowMissingVariables);
ctx.manifestFilePath = `${ctx.manifestFilePath}.processed`;
await saveToFile(ctx.manifestFilePath, JSON.stringify(processedManifest));

task.output = `Zipping your manifest file`;
const zipFilePath = await compressFilesToZip([{ path: ctx.manifestFilePath, replaceName: 'manifest.json' }]);
zipFilePath = await compressFilesToZip([{ path: ctx.manifestFilePath, replaceName: 'manifest.json' }]);
const buffer = readZipFileAsBuffer(zipFilePath);
task.output = `Uploading your app manifest`;
await uploadZippedManifest(buffer, { appId: ctx.appId, appVersionId: ctx.appVersionId });
task.output = `your app manifest has been uploaded successfully`;
} finally {
if (zipFilePath) {
try {
await unlink(zipFilePath);
} catch {
// best-effort cleanup of temp zip
}
}
await unlink(ctx.manifestFilePath);
}
};
Expand Down