Skip to content

Gateway sync marks a completed assignment COMPLETE without importing it when completedAt or symmetricKey is missing #1637

Open

Description

@joshunrau

When the gateway reports a completed remote assignment that has encryptedData but a null completedAt or symmetricKey, GatewaySynchronizer.handleAssignmentComplete logs an error and returns normally. sync() treats that as success and calls assignmentsService.updateStatusById(id, 'COMPLETE'). The local assignment is then shown to clinicians as complete, but no session or instrument record was created, and the remote assignment, which still holds the patient's encrypted responses, is never deleted or imported. Each later pass repeats the same log-and-mark-complete cycle. A missing encryptedData is handled differently: it throws, so the status stays untouched and the assignment is retried, which is what the comment in sync() describes. The current gateway writes all three fields together, so reaching this state needs a gateway defect, a version mismatch or edited gateway data. But when it is reached, the result is a submission that looks imported and is not.

Where

apps/api/src/gateway/gateway.synchronizer.ts:110-116:

if (!remoteAssignment.completedAt) {
  this.loggingService.error(`Field 'completedAt' is null for assignment '${assignment.id}'`);
  return;
} else if (!remoteAssignment.symmetricKey) {
  this.loggingService.error(`Field 'symmetricKey' is null for assignment '${assignment.id}'`);
  return;
}

This is followed by apps/api/src/gateway/gateway.synchronizer.ts:89-94:

} else if (assignment.status === 'COMPLETE') {
  await this.handleAssignmentComplete(assignment);
} ...
await this.assignmentsService.updateStatusById(assignment.id, assignment.status);

Compare apps/api/src/gateway/gateway.synchronizer.ts:105-107, which throws for missing encryptedData.

Reproduce

Have gatewayService.fetchRemoteAssignments return a remote assignment with status: 'COMPLETE', valid encryptedData, and completedAt: null (or symmetricKey: null). Then run sync().

  • Actual: an error is logged, sessionsService.create is not called, gatewayService.deleteRemoteAssignment is not called, and assignmentsService.updateStatusById(id, 'COMPLETE') is called.
  • Expected: updateStatusById is not called and the error is logged as a failure to synchronize that assignment. The local status stays OUTSTANDING, so the problem stays visible and is retried, matching the missing-encryptedData case.

Tests

apps/api/src/gateway/__tests__/gateway.synchronizer.spec.ts, describe('incomplete submission'), tests 'should create no session for a submission without a completion date' and 'should create no session for a submission without a symmetric key'. Neither one asserts on updateStatusById, so they do not pin the defect. When fixing:

  • add expect(assignmentsService.updateStatusById).not.toHaveBeenCalled() to both;
  • change the logged-error assertion to the { error: 'Failed to synchronize remote assignment with ID: assignment-1' } shape, as in the sibling encryptedData: null test, if the fix throws instead of logging.

Suggested fix

Throw instead of logging and returning, as the encryptedData check already does, and move both checks above the findById call so all three missing-field cases are handled in one place.

Found while raising unit test coverage in #1632.

Activity

  1. added
    BugType: existing behavior is wrong
    Difficulty: LowIsolated change, about 2 hours or less
    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)
    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 lessPriority: MediumReal value; schedule normally

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions