Search code examples
typescripttestingautomationjestjssupertest

Attach a file from local storage using Jest+Supertest


I want to upload a file to endpoint with PUT request using Jest + Supertest.

I have following request constructor:

export async function uploadFileForJob(
  uploadUrl: string,
  accessToken: string,
  filePath: string
) {
  const upload_file_for_job = await request(uploadUrl)
    .put("")
    .set("Authorization", `Bearer ${accessToken}`)
    .set("Content-Type", "model/gltf-binary")
    .attach('file', filePath)
  return upload_file_for_job;
}

and the request in test file itself:

const UploadFileForJob = await uploadFileForJob(
      uploadUrl,
      accessToken,
      'C:/Job/Simulations/TestTshirt_04.glb'
    );
    expect(UploadFileForJob.status).toEqual(200);

The endpoint always returns 200 to me and has no body in response, so I have another way of checking if file was actually correctly uploaded:

export async function getJobView(jobId: string, accessToken: string) {
  const get_job_view = await request(`https://${config.url}`)
    .get(`/products-v0/_objects/${jobId}/Views/File`)
    .set("Authorization", `Bearer ${accessToken}`)
    .send();
  return get_job_view;
}
    const GetJobView = await getJobView(jobId, accessToken);
    expect(GetJobView.status).toEqual(200);
    expect(GetJobView.body.view).toBeTruthy();

and here I already get 404 error which indicates that file wasn't correctly accepted by previous endpoint. I am sure that the error lies in a way how jest takes the route which I point to it and there are no errors in any variables, if I use all the same with Postman (using binary body file selector) everything works perfectly. What should be the way of writing this .attach in a correct way?


Solution

  • Finally solution is found. There were two problems. First was that uploading of a file takes some time, so next request should be sent after uploading of the file is finished, for it I used do...while loop with some retries checking that file is finally attached.

        // Wait for the file to be attached to the job
    let JobViewResponse;
    const maxRetries = 20; // Number of retries before giving up
    let retries = 0;
    
    do {
      // Introduce a small delay between each retry (e.g., 2 seconds)
      await sleep(2000);
    
      JobViewResponse = await getJobView(jobId, accessToken);
      retries++;
    } while (JobViewResponse.status !== 200 && retries < maxRetries);
    
    // Check if the file is attached to the job
    if (JobViewResponse.status === 200 && JobViewResponse.body.view) {
      const viewData = JSON.parse(JobViewResponse.body.view);
      expect(JobViewResponse.status).toEqual(200);
      expect(JobViewResponse.body.view).toBeTruthy();
      expect(viewData.Size).toBeGreaterThan(0);
      console.log("File is attached to the job.");
    } else {
      console.error("File attachment to the job failed or took too long.");
    }
    

    Second problem was that attach() tries to send the file as multipart form data. But in our case, the request body should only contain the raw bytes. To solve it, I used fs.readFileSync to read the file in memory first.

        export async function uploadFileForJob(
      uploadUrl: string,
      accessToken: string,
      filePath: string
    ) {
      const fileContents = fs.readFileSync(filePath);
      const upload_file_for_job = await request(uploadUrl)
        .put("")
        .set("Authorization", `Bearer ${accessToken}`)
        .set("Content-Type", "model/gltf-binary")
        .send(fileContents);
      return upload_file_for_job;
    }