test/e2e/api/projectfiles.e2e.spec.tsmodifiedimport {
expect,
app,
http,
Server,
cleanMockSetup,
closeMockServer,
createAuthToken,
setupIAM,
setupE2EMockEnvVariables,
createAzureContainerClient,
authoriseProjectIamAccess,
Authorities,
TestProjects,
chai,
uuidv4,
} from "../common/e2e-imports";
const FILE_RESOURCE = `${__dirname}/../../resources/upload-file.txt`;
const PROJECT_ID = TestProjects.ProjectId1;
const EMPTY_PROJECT_ID = TestProjects.ProjectId6;
/* eslint-disable @typescript-eslint/no-explicit-any */
describe("Project Files API E2E tests", () => {
let server: Server;
let authToken: any;
before(async () => {
setupE2EMockEnvVariables();
await cleanMockSetup();
await setupIAM();
server = http.createServer(app);
server.listen();
authToken = await createAuthToken("abc");
await createAzureContainerClient();
});
after(() => {
closeMockServer(server);
});
// -------------------------------------------------------------------------
describe("POST /api/v2/projects/:projectId/files - Upload file", () => {
beforeEach(async () => {
await cleanMockSetup();
await setupIAM();
});
it("Should return 201 and fileReferenceId when a single-chunk file is uploaded successfully", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Test File")
.field("fileType", "Report")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(response.status).to.equal(201);
expect(response.body).to.have.property("fileReferenceId");
expect(response.body.fileReferenceId).to.be.a("string");
expect(response.body).to.have.property("message", "Project file uploaded successfully");
});
it("Should return 200 on intermediate chunk and 201 on final chunk for multi-chunk upload", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const chunk1 = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Chunked File")
.field("fileType", "Dashboard")
.field("chunkIndex", "0")
.field("totalChunks", "2")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(chunk1.status).to.equal(200);
expect(chunk1.body).to.have.property("fileReferenceId");
expect(chunk1.body).to.have.property("message", "Project file chunk uploaded successfully");
const fileReferenceId = chunk1.body.fileReferenceId;
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const chunk2 = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Chunked File")
.field("fileType", "Dashboard")
.field("chunkIndex", "1")
.field("totalChunks", "2")
.field("fileReferenceId", fileReferenceId)
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(chunk2.status).to.equal(201);
expect(chunk2.body).to.have.property("fileReferenceId", fileReferenceId);
expect(chunk2.body).to.have.property("message", "Project file uploaded successfully");
});
it("Should return 400 when no file is attached", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Test File")
.field("fileType", "Report")
.field("chunkIndex", "0")
.field("totalChunks", "1");
expect(response.status).to.equal(400);
expect(response.body).to.have.property("code", "InvalidRequest");
});
it("Should return 400 when fileName is missing", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("xyzDisplayName", "Test File")
.field("fileType", "Report")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(response.status).to.equal(400);
expect(response.body).to.have.property("code", "InvalidRequest");
expect(response.body.message).to.include("fileName");
});
it("Should return 400 when xyzDisplayName is missing", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("fileType", "Report")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(response.status).to.equal(400);
expect(response.body).to.have.property("code", "InvalidRequest");
expect(response.body.message).to.include("xyzDisplayName");
});
it("Should return 400 when chunkIndex > 0 and fileReferenceId is not provided", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Test File")
.field("fileType", "Report")
.field("chunkIndex", "1")
.field("totalChunks", "3")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(response.status).to.equal(400);
expect(response.body).to.have.property("code", "InvalidRequest");
});
it("Should return 401 when Authorization header is missing", async () => {
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Test File")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(response.status).to.equal(401);
});
it("Should return 403 when caller lacks IMAGE_UPLOAD authority", async () => {
const response = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Test File")
.field("fileType", "Report")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(response.status).to.equal(403);
});
});
// -------------------------------------------------------------------------
describe("GET /api/v2/projects/:projectId/files - List files", () => {
let uploadedFileReferenceId: string;
before(async () => {
await cleanMockSetup();
await setupIAM();
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const upload = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Listed File")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(upload.status).to.equal(201);
uploadedFileReferenceId = upload.body.fileReferenceId;
});
beforeEach(async () => {
await cleanMockSetup();
await setupIAM();
});
it("Should return 200 with an array containing the uploaded file metadata", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(200);
expect(response.body).to.be.an("array");
expect(response.body.length).to.be.at.least(1);
const file = response.body.find((f: any) => f.fileReferenceId === uploadedFileReferenceId);
expect(file).to.not.be.undefined;
expect(file).to.have.property("fileReferenceId", uploadedFileReferenceId);
expect(file).to.have.property("fileName", "upload-file.txt");
expect(file).to.have.property("fileExtension", "txt");
expect(file).to.have.property("xyzDisplayName", "Listed File");
expect(file).to.have.property("insertedOn");
expect(file).to.not.have.property("downloadUrl");
});
it("Should return 200 with an empty array for a project that has no files", async () => {
await authoriseProjectIamAccess(EMPTY_PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${EMPTY_PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(200);
console.log(response.body);
expect(response.body).to.be.an("array").that.is.empty;
});
it("Should return 401 when Authorization header is missing", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files`);
expect(response.status).to.equal(401);
});
it("Should return 403 when caller lacks IMAGE_VIEW authority", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(403);
});
});
// -------------------------------------------------------------------------
describe("GET /api/v2/projects/:projectId/files/:fileReferenceId - Get file", () => {
let uploadedFileReferenceId: string;
before(async () => {
await cleanMockSetup();
await setupIAM();
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const upload = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Single File")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(upload.status).to.equal(201);
uploadedFileReferenceId = upload.body.fileReferenceId;
});
beforeEach(async () => {
await cleanMockSetup();
await setupIAM();
});
it("Should return 200 with file details and a downloadUrl", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/${uploadedFileReferenceId}`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(200);
expect(response.body).to.have.property("fileReferenceId", uploadedFileReferenceId);
expect(response.body).to.have.property("fileName", "upload-file.txt");
expect(response.body).to.have.property("fileExtension", "txt");
expect(response.body).to.have.property("xyzDisplayName", "Single File");
expect(response.body).to.have.property("fileSizeBytes").that.is.a("number");
expect(response.body).to.have.property("createdBy");
expect(response.body).to.have.property("insertedOn");
expect(response.body).to.have.property("downloadUrl").that.is.a("string");
});
it("Should return 404 when the fileReferenceId does not exist", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/${uuidv4()}`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(404);
});
it("Should return 400 when fileReferenceId is not a valid UUID", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/not-a-uuid`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(400);
});
it("Should return 401 when Authorization header is missing", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/${uploadedFileReferenceId}`);
expect(response.status).to.equal(401);
});
it("Should return 403 when caller lacks IMAGE_VIEW authority", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/${uploadedFileReferenceId}`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(403);
});
});
// -------------------------------------------------------------------------
describe("GET /api/v2/projects/:projectId/files/biggest - Biggest file", () => {
before(async () => {
await cleanMockSetup();
await setupIAM();
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_UPLOAD]);
const upload = await chai
.request(app)
.post(`/api/v2/projects/${PROJECT_ID}/files`)
.set("Authorization", `Bearer ${authToken.token}`)
.set("content-type", "multipart/form-data")
.field("fileName", "upload-file.txt")
.field("xyzDisplayName", "Biggest Candidate")
.field("chunkIndex", "0")
.field("totalChunks", "1")
.attach("file", FILE_RESOURCE, "upload-file.txt");
expect(upload.status).to.equal(201);
});
beforeEach(async () => {
await cleanMockSetup();
await setupIAM();
});
it("Should return 200 with the biggest file's metadata for a project that has files", async () => {
await authoriseProjectIamAccess(PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/biggest`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(200);
expect(response.body).to.be.an("object");
expect(response.body).to.have.property("fileReferenceId").that.is.a("string");
expect(response.body).to.have.property("fileName");
expect(response.body).to.have.property("fileExtension");
expect(response.body).to.have.property("xyzDisplayName");
expect(response.body).to.have.property("fileSizeBytes").that.is.a("number");
expect(response.body).to.have.property("insertedOn");
expect(response.body).to.not.have.property("downloadUrl");
});
it("Should return 200 with a null body for a project that has no files", async () => {
await authoriseProjectIamAccess(EMPTY_PROJECT_ID, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${EMPTY_PROJECT_ID}/files/biggest`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(200);
expect(response.body).to.be.null;
});
it("Should return 404 when the project does not exist", async () => {
const missingProjectId = uuidv4();
await authoriseProjectIamAccess(missingProjectId, authToken.token, [Authorities.IMAGE_VIEW]);
const response = await chai
.request(app)
.get(`/api/v2/projects/${missingProjectId}/files/biggest`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(404);
});
it("Should return 400 when projectId is not a valid UUID", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/not-a-uuid/files/biggest`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(400);
});
it("Should return 401 when Authorization header is missing", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/biggest`);
expect(response.status).to.equal(401);
});
it("Should return 403 when caller lacks IMAGE_VIEW authority", async () => {
const response = await chai
.request(app)
.get(`/api/v2/projects/${PROJECT_ID}/files/biggest`)
.set("Authorization", `Bearer ${authToken.token}`);
expect(response.status).to.equal(403);
});
});
});