Some checks failed
UnitTCMS CI / unittcms-test (push) Has been cancelled
JSON import lets users round-trip cases exported via the JSON download endpoint back into a folder, alongside the existing Excel import path.
164 lines
4.7 KiB
JavaScript
164 lines
4.7 KiB
JavaScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import request from 'supertest';
|
|
import express from 'express';
|
|
import { Sequelize } from 'sequelize';
|
|
import casesImportRoute from './import.js';
|
|
|
|
// mock of authentication / permission middleware
|
|
vi.mock('../../middleware/auth.js', () => ({
|
|
default: () => ({
|
|
verifySignedIn: vi.fn((req, res, next) => {
|
|
req.userId = 1;
|
|
next();
|
|
}),
|
|
}),
|
|
}));
|
|
vi.mock('../../middleware/verifyEditable.js', () => ({
|
|
default: () => ({
|
|
verifyProjectDeveloperFromFolderId: vi.fn((req, res, next) => {
|
|
next();
|
|
}),
|
|
}),
|
|
}));
|
|
|
|
const mockCase = {
|
|
bulkCreate: vi.fn(),
|
|
belongsToMany: vi.fn(),
|
|
};
|
|
vi.mock('../../models/cases.js', () => ({
|
|
default: () => mockCase,
|
|
}));
|
|
|
|
const mockStep = {
|
|
create: vi.fn(),
|
|
belongsToMany: vi.fn(),
|
|
};
|
|
vi.mock('../../models/steps.js', () => ({
|
|
default: () => mockStep,
|
|
}));
|
|
|
|
const mockCaseStep = {
|
|
create: vi.fn(),
|
|
};
|
|
vi.mock('../../models/caseSteps.js', () => ({
|
|
default: () => mockCaseStep,
|
|
}));
|
|
|
|
describe('POST /import with JSON file', () => {
|
|
let app;
|
|
const sequelize = new Sequelize({ dialect: 'sqlite', logging: false });
|
|
|
|
beforeEach(() => {
|
|
app = express();
|
|
app.use(express.json());
|
|
app.use('/', casesImportRoute(sequelize));
|
|
vi.clearAllMocks();
|
|
vi.spyOn(sequelize, 'transaction').mockResolvedValue({
|
|
commit: vi.fn(),
|
|
rollback: vi.fn(),
|
|
});
|
|
});
|
|
|
|
const attachJson = (data) =>
|
|
request(app)
|
|
.post('/import?folderId=1')
|
|
.attach('file', Buffer.from(JSON.stringify(data)), 'cases.json');
|
|
|
|
it('creates cases from a JSON array, mapping labels to enum indexes', async () => {
|
|
mockCase.bulkCreate.mockResolvedValue([{ id: 101 }, { id: 102 }]);
|
|
mockStep.create.mockResolvedValue({ id: 201 });
|
|
mockCaseStep.create.mockResolvedValue({});
|
|
|
|
const response = await attachJson([
|
|
{
|
|
title: 'Text case',
|
|
priority: 'high',
|
|
type: 'functional',
|
|
template: 'text',
|
|
description: 'desc',
|
|
Steps: [],
|
|
},
|
|
{
|
|
title: 'Step case',
|
|
priority: 'medium',
|
|
type: 'acceptance',
|
|
template: 'step',
|
|
automationStatus: 'cannot-be-automated',
|
|
Steps: [
|
|
{ step: 'do A', expectedStepResult: 'A done' },
|
|
{ step: 'do B', expectedStepResult: 'B done' },
|
|
],
|
|
},
|
|
]);
|
|
|
|
expect(response.status).toBe(200);
|
|
|
|
const created = mockCase.bulkCreate.mock.calls[0][0];
|
|
expect(created).toHaveLength(2);
|
|
expect(created[0]).toMatchObject({
|
|
folderId: '1',
|
|
title: 'Text case',
|
|
state: 0,
|
|
priority: 1, // high
|
|
type: 4, // functional
|
|
template: 0, // text
|
|
automationStatus: 1, // default automation-not-required
|
|
});
|
|
expect(created[1]).toMatchObject({
|
|
title: 'Step case',
|
|
priority: 2, // medium
|
|
type: 5, // acceptance
|
|
template: 1, // step
|
|
automationStatus: 2, // cannot-be-automated
|
|
});
|
|
|
|
// Only the second case has steps -> 2 steps created
|
|
expect(mockStep.create).toHaveBeenCalledTimes(2);
|
|
expect(mockCaseStep.create).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('accepts the object-map shape produced by the JSON export', async () => {
|
|
mockCase.bulkCreate.mockResolvedValue([{ id: 101 }]);
|
|
|
|
const response = await attachJson({
|
|
1: { title: 'Mapped case', priority: 'low', type: 'other', template: 'text', Steps: [] },
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(mockCase.bulkCreate.mock.calls[0][0]).toHaveLength(1);
|
|
});
|
|
|
|
it('returns 400 when a required field is missing', async () => {
|
|
const response = await attachJson([{ priority: 'high', type: 'functional', template: 'text' }]);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.error).toContain('missing required field: title');
|
|
expect(mockCase.bulkCreate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns 400 for an invalid priority label', async () => {
|
|
const response = await attachJson([
|
|
{ title: 'Bad priority', priority: 'urgent', type: 'functional', template: 'text' },
|
|
]);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.error).toContain('invalid priority');
|
|
});
|
|
|
|
it('returns 400 for malformed JSON', async () => {
|
|
const response = await request(app)
|
|
.post('/import?folderId=1')
|
|
.attach('file', Buffer.from('{ not json'), 'cases.json');
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.error).toBe('Invalid JSON file');
|
|
});
|
|
|
|
it('rejects unsupported file extensions', async () => {
|
|
const response = await request(app).post('/import?folderId=1').attach('file', Buffer.from('hello'), 'cases.txt');
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.error).toContain('Only Excel');
|
|
});
|
|
});
|