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'); }); });