Skip to content

Commit

Permalink
API-13315: fix FilledForm answer field (v3.5) (#159)
Browse files Browse the repository at this point in the history
* API-13315: Fix FilledForm answer field (v3.5)

* fix issues found during review

* fixup! fix issues found during review

* check nil FormType before unmarshalling and add tests

---------

Co-authored-by: kacperf531 <[email protected]>
Co-authored-by: Wojciech Fabjańczuk <[email protected]>
  • Loading branch information
3 people authored Apr 19, 2024
1 parent 7653c0f commit e271928
Show file tree
Hide file tree
Showing 6 changed files with 804 additions and 22 deletions.
150 changes: 142 additions & 8 deletions agent/structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,8 @@ type Event struct {

// FilledForm represents LiveChat filled form event.
type FilledForm struct {
Fields []struct {
ID string `json:"id"`
Label string `json:"label"`
Type string `json:"type"`
Value string `json:"value"`
} `json:"fields"`
FormType string `json:"form_type"`
Fields []FilledFormField `json:"fields"`
FormType string `json:"form_type"`
Event
}

Expand All @@ -434,12 +429,151 @@ func (e *Event) FilledForm() *FilledForm {
if err := json.Unmarshal(e.Fields, &f.Fields); err != nil {
return nil
}
if err := json.Unmarshal(e.FormType, &f.FormType); err != nil {
if err := internal.UnmarshalOptionalRawField(e.FormType, &f.FormType); err != nil {
return nil
}
return &f
}

// FilledFormField represents a field in LiveChat filled form event.
type FilledFormField struct {
ID string `json:"id"`
Type string `json:"type"`
Label string `json:"label"`
filledFormFieldSpecific
}

type filledFormFieldSpecific struct {
Answer json.RawMessage `json:"answer,omitempty"`
Answers json.RawMessage `json:"answers,omitempty"`
}

// FilledFormFieldSingle represents a field in LiveChat filled form event with a single answer (e.g. text field).
type FilledFormFieldSingle struct {
FilledFormField
Answer string `json:"answer"`
}

// FilledFormFieldSingleChoice represents a field in LiveChat filled form event with a single choice answer (e.g. radio button).
type FilledFormFieldSingleChoice struct {
FilledFormField
Answer struct {
ID string `json:"id"`
Label string `json:"label"`
} `json:"answer"`
}

// FilledFormFieldMultiChoice represents a field in LiveChat filled form event with a multiple choice answer (e.g. checkbox).
type FilledFormFieldMultiChoice struct {
FilledFormField
Answers []struct {
ID string `json:"id"`
Label string `json:"label"`
} `json:"answer"`
}

// FilledFormEmailCheckbox represents a field in LiveChat filled form event with a checkbox for email answer.
type FilledFormEmailCheckbox struct {
FilledFormField
Answer bool `json:"answer"`
}

// FilledFormFieldGroupChooser represents a field in LiveChat filled form event with a group_chooser answer.
type FilledFormFieldGroupChooser struct {
FilledFormField
Answer struct {
ID string `json:"id"`
Label string `json:"label"`
GroupID int `json:"group_id"`
} `json:"answer"`
}

// Single method converts FilledFormField object to FilledFormFieldSingle object
// if FilledFormField's Type is one of "name", "email", "question", "textarea", "subject".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) Single() *FilledFormFieldSingle {
supportedTypes := map[string]struct{}{
"name": {},
"email": {},
"question": {},
"textarea": {},
"subject": {},
}
if _, ok := supportedTypes[f.Type]; !ok {
return nil
}
var s FilledFormFieldSingle
s.ID, s.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &s.Answer); err != nil {
return nil
}
return &s
}

// SingleChoice method converts FilledFormField object to FilledFormFieldSingleChoice object
// if FilledFormField's Type is "radio" or "select".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) SingleChoice() *FilledFormFieldSingleChoice {
supportedTypes := map[string]struct{}{
"radio": {},
"select": {},
}
if _, ok := supportedTypes[f.Type]; !ok {
return nil
}
var sc FilledFormFieldSingleChoice
sc.ID, sc.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &sc.Answer); err != nil {
return nil
}
return &sc
}

// MultiChoice method converts FilledFormField object to FilledFormFieldMultiChoice object
// if FilledFormField's Type is "checkbox".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) MultiChoice() *FilledFormFieldMultiChoice {
if f.Type != "checkbox" {
return nil
}
var mc FilledFormFieldMultiChoice
mc.ID, mc.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answers, &mc.Answers); err != nil {
return nil
}
return &mc
}

// EmailCheckbox method converts FilledFormField object to FilledFormEmailCheckbox object
// if FilledFormField's Type is "checkbox_for_email".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) EmailCheckbox() *FilledFormEmailCheckbox {
if f.Type != "checkbox_for_email" {
return nil
}
var ec FilledFormEmailCheckbox
ec.ID, ec.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &ec.Answer); err != nil {
return nil
}
return &ec
}

// GroupChooser method converts FilledFormField object to FilledFormFieldGroupChooser object
// if FilledFormField's Type is "group_chooser".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser {
if f.Type != "group_chooser" {
return nil
}
var gc FilledFormFieldGroupChooser
gc.ID, gc.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &gc.Answer); err != nil {
return nil
}
return &gc
}

// Postback represents postback data in LiveChat message event.
type Postback struct {
ID string `json:"id"`
Expand Down
125 changes: 125 additions & 0 deletions agent/structures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package agent_test

import (
"encoding/json"
"fmt"
"testing"

"github.com/livechat/lc-sdk-go/v5/agent"
)

func TestFilledFormTypesOK(t *testing.T) {
rawFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"[email protected]"}]`)
testCases := []struct {
name string
expectedFormType string
rawFormType json.RawMessage
}{
{name: "Missing form_type", expectedFormType: "", rawFormType: nil},
{name: "Null form_type", expectedFormType: "", rawFormType: json.RawMessage(`null`)},
{name: "Empty string form_type", expectedFormType: "", rawFormType: json.RawMessage(`""`)},
{name: "Existing form_type", expectedFormType: "prechat", rawFormType: json.RawMessage(`"prechat"`)},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var suffixFormType string
if tc.rawFormType != nil {
suffixFormType = fmt.Sprintf(`,"form_type":%s`, tc.rawFormType)
}

rawEvent := json.RawMessage(fmt.Sprintf(`{"type":"filled_form","fields":%s%s}`, rawFields, suffixFormType))
filledForm := FilledFormFromJSON(t, rawEvent)

if filledForm.FormType != tc.expectedFormType {
t.Fatalf("FilledForm() did not set FormType correctly. Expected %q, got %q", tc.expectedFormType, filledForm.FormType)
}

if len(filledForm.Fields) != 1 {
t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields))
}
})
}
}

func TestFilledFormFieldTypesOK(t *testing.T) {
t.Run("Single-answer filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"[email protected]"}]`)
singleField := FilledFormFieldFromJSON(t, rawFormFields).Single()

if singleField.Answer != "[email protected]" {
t.Errorf("FilledFormField() did not return expected answer: %s", singleField.Answer)
}
})

t.Run("Single-choice filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "radio", "answer":{"id": "1", "label": "foo"}}]`)
singleChoiceField := FilledFormFieldFromJSON(t, rawFormFields).SingleChoice()

if singleChoiceField.Answer.ID != "1" || singleChoiceField.Answer.Label != "foo" {
t.Errorf("FilledFormField() did not return expected answer: %s", singleChoiceField.Answer)
}
})

t.Run("Multiple-choice filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "checkbox", "answers":[{"id": "1", "label": "foo"}, {"id": "2", "label": "bar"}]}]`)
multiChoiceField := FilledFormFieldFromJSON(t, rawFormFields).MultiChoice()

if len(multiChoiceField.Answers) != 2 {
t.Errorf("FilledFormField() did not return expected number of answers: %d", len(multiChoiceField.Answer))
}
if multiChoiceField.Answers[0].ID != "1" || multiChoiceField.Answers[0].Label != "foo" {
t.Errorf("FilledFormField() did not return expected answer: %s", multiChoiceField.Answers[0])
}
if multiChoiceField.Answers[1].ID != "2" || multiChoiceField.Answers[1].Label != "bar" {
t.Errorf("FilledFormField() did not return expected answer: %s", multiChoiceField.Answers[1])
}
})

t.Run("Email checkbox filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "checkbox_for_email", "answer": true}]`)
emailCheckboxField := FilledFormFieldFromJSON(t, rawFormFields).EmailCheckbox()

if !emailCheckboxField.Answer {
t.Errorf("FilledFormField() did not return expected answer: %t", emailCheckboxField.Answer)
}
})

t.Run("Group chooser filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "group_chooser", "answer":{"id": "1", "label": "foo", "group_id": 42}}]`)
groupChooserField := FilledFormFieldFromJSON(t, rawFormFields).GroupChooser()

if groupChooserField.Answer.ID != "1" || groupChooserField.Answer.Label != "foo" || groupChooserField.Answer.GroupID != 42 {
t.Errorf("FilledFormField() did not return expected answer: %v", groupChooserField.Answer)
}
})
}

func FilledFormFromJSON(t *testing.T, rawEvent json.RawMessage) *agent.FilledForm {
t.Helper()

var event agent.Event
if err := json.Unmarshal(rawEvent, &event); err != nil {
t.Fatalf("unmarshalling base event failed: %s", err)
}

filledForm := event.FilledForm()
if filledForm == nil {
t.Fatalf("FilledForm() returned nil")
}

return filledForm
}

func FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *agent.FilledFormField {
t.Helper()

rawEvent := json.RawMessage(fmt.Sprintf(`{"type":"filled_form","fields":%s}`, rawFields))
filledForm := FilledFormFromJSON(t, rawEvent)

if len(filledForm.Fields) != 1 {
t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields))
}

return &filledForm.Fields[0]
}
Loading

0 comments on commit e271928

Please sign in to comment.