Skip to content

Upload completion returns 500 when an optional file group is omitted #1648

Open

Description

@joshunrau

POST /v1/instrument-records/:id/files/upload-complete returns a 500 when the payload leaves out an optional file group (one whose count.min is 0). validateFiles accepts the missing group in its count check (it counts it as 0 files), then iterates it through a non-null assertion and throws TypeError: uploadedFiles is not iterable. The payload is valid under $UploadCompleteData (uploads: z.record(z.string(), z.array($FileMetadata))), so the API should complete the upload. Instead the record stays pending even though the files are already in storage. The web client avoids this only because apps/web/src/routes/_app/instruments/render/$id.tsx (lines 74-76) pre-fills every basename with []. Any other API client that omits an empty optional group hits the crash.

Where

apps/api/src/instrument-records/files/files.service.ts:174-185

const uploadedFiles = uploads[fileGroup.basename];
const actual = uploadedFiles?.length ?? 0;
// count check passes when min === 0 and the key is absent
for (const file of uploadedFiles!) {
  validatedFiles.push(file);
}

Reproduce

  1. Create a pending record for a file instrument with a group whose count is { min: 0, max: 2 } (for example, basename notes).
  2. POST /v1/instrument-records/<id>/files/upload-complete with { "uploads": {} }.

Actual: 500 (TypeError: uploadedFiles is not iterable), and the record stays pending.
Expected: 201, and the record is marked complete with no files for notes.

Tests

apps/api/src/instrument-records/files/__tests__/files.service.spec.ts, test should crash with a type error when an optional group is omitted, documenting a bug in validateFiles (line 322). It asserts .rejects.toThrow(TypeError). When fixing, rename it to describe the correct behaviour (for example, "should complete the upload when an optional group is omitted"). Then assert that instrumentRecordModel.update is called with files: { create: [] } and pending: false.

Suggested fix

Default the lookup to an empty array (const uploadedFiles = uploads[fileGroup.basename] ?? [];) and drop the !.

Found while raising unit test coverage in #1632.

Activity

  1. added
    BugType: existing behavior is wrong
    Priority: LowNice to have, cosmetic or speculative
    Difficulty: LowIsolated change, about 2 hours or less
    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)
    Good First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributor
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)BugType: existing behavior is wrongDifficulty: LowIsolated change, about 2 hours or lessGood First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributorPriority: LowNice to have, cosmetic or speculative

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions