From 55df6b8b0b1060d731794675edd9f39c5cc0fe8a Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Mon, 8 Jun 2026 02:20:11 -0700 Subject: [PATCH] fix: allow audio-only dynamic questions without text stimulus DYNAMIC question create no longer requires QUESTION_TEXT, INSTRUCTION, or TEXT_PASSAGE in dynamic_payload; validation follows the type definition only. Co-authored-by: Cursor --- internal/domain/question_type_builder.go | 7 ++--- internal/domain/question_type_builder_test.go | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/internal/domain/question_type_builder.go b/internal/domain/question_type_builder.go index 7c76231..1be461e 100644 --- a/internal/domain/question_type_builder.go +++ b/internal/domain/question_type_builder.go @@ -573,13 +573,14 @@ func ValidateQuestionTextNotAllowedForDynamic(questionType string, explicit stri return nil } if strings.TrimSpace(explicit) != "" { - return fmt.Errorf("question_text is not used for DYNAMIC questions; set prompt text in dynamic_payload stimulus (QUESTION_TEXT, INSTRUCTION, or TEXT_PASSAGE)") + return fmt.Errorf("question_text is not used for DYNAMIC questions; set prompt content in dynamic_payload stimulus per the selected type definition") } return nil } // ResolveDynamicStoredQuestionText returns the questions.question_text column value for DYNAMIC rows -// (search/activity logs only; clients read prompt text from dynamic_payload). +// (search/activity logs only; clients read prompt content from dynamic_payload). +// When the definition has no text stimulus kinds, an empty string is stored. func ResolveDynamicStoredQuestionText(payload *DynamicQuestionPayload, existing string) (string, error) { if derived := StimulusTextFromPayload(payload); derived != "" { return derived, nil @@ -587,7 +588,7 @@ func ResolveDynamicStoredQuestionText(payload *DynamicQuestionPayload, existing if strings.TrimSpace(existing) != "" { return strings.TrimSpace(existing), nil } - return "", fmt.Errorf("dynamic_payload must include prompt text (QUESTION_TEXT, INSTRUCTION, or TEXT_PASSAGE)") + return "", nil } // QuestionTextJSONField returns question_text for API responses. Omitted (nil) for DYNAMIC questions. diff --git a/internal/domain/question_type_builder_test.go b/internal/domain/question_type_builder_test.go index 18340ca..bb3ac6c 100644 --- a/internal/domain/question_type_builder_test.go +++ b/internal/domain/question_type_builder_test.go @@ -237,10 +237,28 @@ func TestResolveDynamicStoredQuestionText_fromINSTRUCTIONStimulus(t *testing.T) } } -func TestResolveDynamicStoredQuestionText_missingText(t *testing.T) { - _, err := ResolveDynamicStoredQuestionText(&DynamicQuestionPayload{}, "") - if err == nil { - t.Fatal("expected error when no prompt in payload") +func TestResolveDynamicStoredQuestionText_noTextStimulusAllowed(t *testing.T) { + payload := &DynamicQuestionPayload{ + Stimulus: []DynamicElementInstance{ + {ID: "audio_prompt_1", Kind: "AUDIO_PROMPT", Value: "https://cdn.example.com/audio/prompt.mp3"}, + }, + } + got, err := ResolveDynamicStoredQuestionText(payload, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != "" { + t.Fatalf("expected empty stored text for audio-only payload, got %q", got) + } +} + +func TestResolveDynamicStoredQuestionText_emptyPayload(t *testing.T) { + got, err := ResolveDynamicStoredQuestionText(&DynamicQuestionPayload{}, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != "" { + t.Fatalf("expected empty stored text, got %q", got) } }