fix: handle empty candidate trait lists

Return empty JSON arrays from the API and normalize nullable values from older servers before rendering candidate match counts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Daniel Noulet 2026-09-19 21:34:12 +02:00
parent 6969f79015
commit fd06fbbb5d
4 changed files with 36 additions and 3 deletions

View file

@ -66,9 +66,17 @@ func ParticipantToWeb(value servicemodel.Participant) webmodel.ParticipantRespon
}
func CandidateToWeb(value servicemodel.CandidateMatch) webmodel.CandidateResponse {
matched := value.Matched
if matched == nil {
matched = []string{}
}
missing := value.Missing
if missing == nil {
missing = []string{}
}
return webmodel.CandidateResponse{
Participant: ParticipantToWeb(value.Participant), Score: value.Score,
Matched: value.Matched, Missing: value.Missing,
Matched: matched, Missing: missing,
}
}

View file

@ -19,3 +19,10 @@ func TestInterviewOmitsReferenceAnswer(t *testing.T) {
t.Fatalf("expected participant-safe code question, got %#v", response)
}
}
func TestCandidateAlwaysUsesJSONArrays(t *testing.T) {
response := CandidateToWeb(servicemodel.CandidateMatch{})
if response.Matched == nil || response.Missing == nil {
t.Fatalf("candidate trait collections must not be nil: %#v", response)
}
}

View file

@ -69,7 +69,7 @@ function mockApi() {
participant,
score: 90,
matchedTraits: ['Go'],
missingTraits: [],
missingTraits: null,
},
])
if (path === '/api/jobs/7/invitations' && method === 'GET') return response([])
@ -129,6 +129,17 @@ describe('Go Guess frontend', () => {
mockApi()
})
it('renders candidate matches when an older API returns null trait arrays', async () => {
authenticate()
const user = userEvent.setup()
renderApp('/jobs/7')
await user.click(await screen.findByRole('tab', { name: /candidate match/i }))
expect(await screen.findByText('90%')).toBeInTheDocument()
expect(screen.getByText('Missing').nextElementSibling).toHaveTextContent('0')
})
it('persists login, navigates from the login URL, and shows API jobs', async () => {
const user = userEvent.setup()
window.history.replaceState({}, '', '/login')

View file

@ -115,7 +115,14 @@ export const api = {
}),
detachQuestion: (jobId: number, questionId: number) =>
request<void>(`/jobs/${jobId}/questions/${questionId}`, { method: 'DELETE' }),
candidates: (jobId: number) => request<CandidateMatch[]>(`/jobs/${jobId}/candidates`),
candidates: async (jobId: number) => {
const matches = await request<CandidateMatch[]>(`/jobs/${jobId}/candidates`)
return matches.map((match) => ({
...match,
matchedTraits: match.matchedTraits ?? [],
missingTraits: match.missingTraits ?? [],
}))
},
invitations: (jobId: number) => request<Invitation[]>(`/jobs/${jobId}/invitations`),
invitation: (jobId: number, invitationId: number) =>
request<Interview>(`/jobs/${jobId}/invitations/${invitationId}`),