From ac8523cbc9fb5ab76f28c4a19fe09d6f9b4a3b0e Mon Sep 17 00:00:00 2001 From: kacperf531 Date: Wed, 3 Apr 2024 16:55:57 +0200 Subject: [PATCH 1/7] API-13315: fix FilledForm answer field --- agent/structures.go | 12 ++++++++---- customer/structures.go | 12 ++++++++---- webhooks/structures.go | 12 ++++++++---- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/agent/structures.go b/agent/structures.go index 9fd1619..684aa18 100644 --- a/agent/structures.go +++ b/agent/structures.go @@ -423,10 +423,14 @@ 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"` + ID string `json:"id"` + Label string `json:"label"` + Type string `json:"type"` + Answer interface{} `json:"answer,omitempty"` // Answer can be string or a JSON object + Answers []struct { + ID string `json:"id"` + Label string `json:"label"` + } `json:"answers,omitempty"` } `json:"fields"` FormType string `json:"form_type"` Event diff --git a/customer/structures.go b/customer/structures.go index b502858..e627607 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -336,10 +336,14 @@ 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"` + ID string `json:"id"` + Label string `json:"label"` + Type string `json:"type"` + Answer interface{} `json:"answer,omitempty"` // Answer can be string or a JSON object + Answers []struct { + ID string `json:"id"` + Label string `json:"label"` + } `json:"answers,omitempty"` } `json:"fields"` FormType string `json:"form_type"` Event diff --git a/webhooks/structures.go b/webhooks/structures.go index dde834d..5ca18a6 100644 --- a/webhooks/structures.go +++ b/webhooks/structures.go @@ -596,10 +596,14 @@ 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"` + ID string `json:"id"` + Label string `json:"label"` + Type string `json:"type"` + Answer interface{} `json:"answer,omitempty"` // Answer can be string or a JSON object + Answers []struct { + ID string `json:"id"` + Label string `json:"label"` + } `json:"answers,omitempty"` } `json:"fields"` Event } From 06bbc08f52f963cb3f5554ecbad0bf34c4c6f1bd Mon Sep 17 00:00:00 2001 From: kacperf531 Date: Tue, 9 Apr 2024 15:38:32 +0200 Subject: [PATCH 2/7] add methods for getting a specific type of filledFormField --- customer/structures.go | 161 ++++++++++++++++++++++++++++++++---- customer/structures_test.go | 78 +++++++++++++++++ 2 files changed, 225 insertions(+), 14 deletions(-) create mode 100644 customer/structures_test.go diff --git a/customer/structures.go b/customer/structures.go index e627607..e464b40 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -303,7 +303,7 @@ type eventSpecific struct { Text json.RawMessage `json:"text"` TextVars json.RawMessage `json:"text_vars"` Fields json.RawMessage `json:"fields"` - FormType json.RawMessage `json:"form_type"` + FormType json.RawMessage `json:"form_type,omitempty"` ContentType json.RawMessage `json:"content_type"` Name json.RawMessage `json:"name"` URL json.RawMessage `json:"url"` @@ -335,17 +335,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"` - Answer interface{} `json:"answer,omitempty"` // Answer can be string or a JSON object - Answers []struct { - ID string `json:"id"` - Label string `json:"label"` - } `json:"answers,omitempty"` - } `json:"fields"` - FormType string `json:"form_type"` + Fields []FilledFormField `json:"fields"` + FormType string `json:"form_type"` Event } @@ -361,10 +352,152 @@ 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 { + f.FormType = string(e.FormType) + 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 } - return &f + var s FilledFormFieldSingle + s.ID = f.ID + s.Label = 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 = f.ID + sc.Label = 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 = f.ID + mc.Label = 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 = f.ID + ec.Label = 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 = f.ID + gc.Label = f.Label + if err := json.Unmarshal(f.Answer, &gc.Answer); err != nil { + return nil + } + return &gc } // Postback represents postback data in LiveChat message event. diff --git a/customer/structures_test.go b/customer/structures_test.go new file mode 100644 index 0000000..d30741d --- /dev/null +++ b/customer/structures_test.go @@ -0,0 +1,78 @@ +package customer_test + +import ( + "encoding/json" + "testing" + + "github.com/livechat/lc-sdk-go/v6/customer" +) + +func TestFilledFormFieldTypesOK(t *testing.T) { + t.Run("Single-answer filled_form field", func(t *testing.T) { + rawFormFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"foo@bar.eu"}]`) + singleField := FilledFormFieldFromJSON(t, rawFormFields).Single() + + if singleField.Answer != "foo@bar.eu" { + 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 FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *customer.FilledFormField { + t.Helper() + + event := customer.Event{Type: "filled_form"} + event.Fields = rawFields + + filledForm := event.FilledForm() + if filledForm == nil { + t.Fatalf("FilledForm() returned nil") + } + if len(filledForm.Fields) != 1 { + t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields)) + } + + return &filledForm.Fields[0] +} From a39d39d00c3adebb6fdc88d80f6a793d9ac452b0 Mon Sep 17 00:00:00 2001 From: kacperf531 Date: Tue, 9 Apr 2024 16:15:11 +0200 Subject: [PATCH 3/7] add filled form field methods to all services --- agent/structures.go | 162 +++++++++++++++++++++++++++++++++--- agent/structures_test.go | 78 +++++++++++++++++ customer/structures.go | 2 +- webhooks/structures.go | 159 ++++++++++++++++++++++++++++++++--- webhooks/structures_test.go | 78 +++++++++++++++++ 5 files changed, 455 insertions(+), 24 deletions(-) create mode 100644 agent/structures_test.go create mode 100644 webhooks/structures_test.go diff --git a/agent/structures.go b/agent/structures.go index 684aa18..b0917a9 100644 --- a/agent/structures.go +++ b/agent/structures.go @@ -422,17 +422,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"` - Answer interface{} `json:"answer,omitempty"` // Answer can be string or a JSON object - Answers []struct { - ID string `json:"id"` - Label string `json:"label"` - } `json:"answers,omitempty"` - } `json:"fields"` - FormType string `json:"form_type"` + Fields []FilledFormField `json:"fields"` + FormType string `json:"form_type"` Event } @@ -448,12 +439,157 @@ 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 { + f.FormType = string(e.FormType) + 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 } - return &f + var s FilledFormFieldSingle + s.ID = f.ID + s.Label = 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 = f.ID + sc.Label = 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 = f.ID + mc.Label = 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 = f.ID + ec.Label = 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 = f.ID + gc.Label = 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"` diff --git a/agent/structures_test.go b/agent/structures_test.go new file mode 100644 index 0000000..2049c6e --- /dev/null +++ b/agent/structures_test.go @@ -0,0 +1,78 @@ +package agent_test + +import ( + "encoding/json" + "testing" + + "github.com/livechat/lc-sdk-go/v6/agent" +) + +func TestFilledFormFieldTypesOK(t *testing.T) { + t.Run("Single-answer filled_form field", func(t *testing.T) { + rawFormFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"foo@bar.eu"}]`) + singleField := FilledFormFieldFromJSON(t, rawFormFields).Single() + + if singleField.Answer != "foo@bar.eu" { + 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 FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *agent.FilledFormField { + t.Helper() + + event := agent.Event{Type: "filled_form"} + event.Fields = rawFields + + filledForm := event.FilledForm() + if filledForm == nil { + t.Fatalf("FilledForm() returned nil") + } + if len(filledForm.Fields) != 1 { + t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields)) + } + + return &filledForm.Fields[0] +} diff --git a/customer/structures.go b/customer/structures.go index e464b40..f26b0ba 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -303,7 +303,7 @@ type eventSpecific struct { Text json.RawMessage `json:"text"` TextVars json.RawMessage `json:"text_vars"` Fields json.RawMessage `json:"fields"` - FormType json.RawMessage `json:"form_type,omitempty"` + FormType json.RawMessage `json:"form_type"` ContentType json.RawMessage `json:"content_type"` Name json.RawMessage `json:"name"` URL json.RawMessage `json:"url"` diff --git a/webhooks/structures.go b/webhooks/structures.go index 5ca18a6..ca35dfc 100644 --- a/webhooks/structures.go +++ b/webhooks/structures.go @@ -595,16 +595,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"` - Answer interface{} `json:"answer,omitempty"` // Answer can be string or a JSON object - Answers []struct { - ID string `json:"id"` - Label string `json:"label"` - } `json:"answers,omitempty"` - } `json:"fields"` + Fields []FilledFormField `json:"fields"` + FormType string `json:"form_type"` Event } @@ -623,6 +615,153 @@ func (e *Event) FilledForm() *FilledForm { 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 = f.ID + s.Label = 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 = f.ID + sc.Label = 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 = f.ID + mc.Label = 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 = f.ID + ec.Label = 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 = f.ID + gc.Label = 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"` diff --git a/webhooks/structures_test.go b/webhooks/structures_test.go new file mode 100644 index 0000000..a992ca5 --- /dev/null +++ b/webhooks/structures_test.go @@ -0,0 +1,78 @@ +package webhooks_test + +import ( + "encoding/json" + "testing" + + "github.com/livechat/lc-sdk-go/v6/webhooks" +) + +func TestFilledFormFieldTypesOK(t *testing.T) { + t.Run("Single-answer filled_form field", func(t *testing.T) { + rawFormFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"foo@bar.eu"}]`) + singleField := FilledFormFieldFromJSON(t, rawFormFields).Single() + + if singleField.Answer != "foo@bar.eu" { + 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 FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *webhooks.FilledFormField { + t.Helper() + + event := webhooks.Event{Type: "filled_form"} + event.Fields = rawFields + + filledForm := event.FilledForm() + if filledForm == nil { + t.Fatalf("FilledForm() returned nil") + } + if len(filledForm.Fields) != 1 { + t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields)) + } + + return &filledForm.Fields[0] +} From 2b043655adff8bb4a57d88825a648668883c22bb Mon Sep 17 00:00:00 2001 From: kacperf531 Date: Tue, 9 Apr 2024 16:19:55 +0200 Subject: [PATCH 4/7] brevity --- agent/structures.go | 16 +++++----------- customer/structures.go | 16 +++++----------- webhooks/structures.go | 15 +++++---------- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/agent/structures.go b/agent/structures.go index b0917a9..a8c88e5 100644 --- a/agent/structures.go +++ b/agent/structures.go @@ -439,7 +439,6 @@ func (e *Event) FilledForm() *FilledForm { if err := json.Unmarshal(e.Fields, &f.Fields); err != nil { return nil } - f.FormType = string(e.FormType) return &f } @@ -513,8 +512,7 @@ func (f *FilledFormField) Single() *FilledFormFieldSingle { return nil } var s FilledFormFieldSingle - s.ID = f.ID - s.Label = f.Label + s.ID, s.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &s.Answer); err != nil { return nil } @@ -533,8 +531,7 @@ func (f *FilledFormField) SingleChoice() *FilledFormFieldSingleChoice { return nil } var sc FilledFormFieldSingleChoice - sc.ID = f.ID - sc.Label = f.Label + sc.ID, sc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &sc.Answer); err != nil { return nil } @@ -549,8 +546,7 @@ func (f *FilledFormField) MultiChoice() *FilledFormFieldMultiChoice { return nil } var mc FilledFormFieldMultiChoice - mc.ID = f.ID - mc.Label = f.Label + mc.ID, mc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answers, &mc.Answers); err != nil { return nil } @@ -565,8 +561,7 @@ func (f *FilledFormField) EmailCheckbox() *FilledFormEmailCheckbox { return nil } var ec FilledFormEmailCheckbox - ec.ID = f.ID - ec.Label = f.Label + ec.ID, ec.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &ec.Answer); err != nil { return nil } @@ -581,8 +576,7 @@ func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser { return nil } var gc FilledFormFieldGroupChooser - gc.ID = f.ID - gc.Label = f.Label + gc.ID, gc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &gc.Answer); err != nil { return nil } diff --git a/customer/structures.go b/customer/structures.go index f26b0ba..c0221ac 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -352,7 +352,6 @@ func (e *Event) FilledForm() *FilledForm { if err := json.Unmarshal(e.Fields, &f.Fields); err != nil { return nil } - f.FormType = string(e.FormType) return &f } @@ -424,8 +423,7 @@ func (f *FilledFormField) Single() *FilledFormFieldSingle { return nil } var s FilledFormFieldSingle - s.ID = f.ID - s.Label = f.Label + s.ID, s.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &s.Answer); err != nil { return nil } @@ -444,8 +442,7 @@ func (f *FilledFormField) SingleChoice() *FilledFormFieldSingleChoice { return nil } var sc FilledFormFieldSingleChoice - sc.ID = f.ID - sc.Label = f.Label + sc.ID, sc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &sc.Answer); err != nil { return nil } @@ -460,8 +457,7 @@ func (f *FilledFormField) MultiChoice() *FilledFormFieldMultiChoice { return nil } var mc FilledFormFieldMultiChoice - mc.ID = f.ID - mc.Label = f.Label + mc.ID, mc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answers, &mc.Answers); err != nil { return nil } @@ -476,8 +472,7 @@ func (f *FilledFormField) EmailCheckbox() *FilledFormEmailCheckbox { return nil } var ec FilledFormEmailCheckbox - ec.ID = f.ID - ec.Label = f.Label + ec.ID, ec.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &ec.Answer); err != nil { return nil } @@ -492,8 +487,7 @@ func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser { return nil } var gc FilledFormFieldGroupChooser - gc.ID = f.ID - gc.Label = f.Label + gc.ID, gc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &gc.Answer); err != nil { return nil } diff --git a/webhooks/structures.go b/webhooks/structures.go index ca35dfc..431d827 100644 --- a/webhooks/structures.go +++ b/webhooks/structures.go @@ -685,8 +685,7 @@ func (f *FilledFormField) Single() *FilledFormFieldSingle { return nil } var s FilledFormFieldSingle - s.ID = f.ID - s.Label = f.Label + s.ID, s.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &s.Answer); err != nil { return nil } @@ -705,8 +704,7 @@ func (f *FilledFormField) SingleChoice() *FilledFormFieldSingleChoice { return nil } var sc FilledFormFieldSingleChoice - sc.ID = f.ID - sc.Label = f.Label + sc.ID, sc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &sc.Answer); err != nil { return nil } @@ -721,8 +719,7 @@ func (f *FilledFormField) MultiChoice() *FilledFormFieldMultiChoice { return nil } var mc FilledFormFieldMultiChoice - mc.ID = f.ID - mc.Label = f.Label + mc.ID, mc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answers, &mc.Answers); err != nil { return nil } @@ -737,8 +734,7 @@ func (f *FilledFormField) EmailCheckbox() *FilledFormEmailCheckbox { return nil } var ec FilledFormEmailCheckbox - ec.ID = f.ID - ec.Label = f.Label + ec.ID, ec.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &ec.Answer); err != nil { return nil } @@ -753,8 +749,7 @@ func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser { return nil } var gc FilledFormFieldGroupChooser - gc.ID = f.ID - gc.Label = f.Label + gc.ID, gc.Label = f.ID, f.Label if err := json.Unmarshal(f.Answer, &gc.Answer); err != nil { return nil } From c0f0a0bb8bad83031f8be48da60c9a0852c38b73 Mon Sep 17 00:00:00 2001 From: kacperf531 Date: Thu, 18 Apr 2024 17:21:22 +0200 Subject: [PATCH 5/7] fix issues found during review --- agent/structures.go | 6 +++--- customer/structures.go | 3 +++ webhooks/structures.go | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/agent/structures.go b/agent/structures.go index a8c88e5..fb99e04 100644 --- a/agent/structures.go +++ b/agent/structures.go @@ -439,10 +439,12 @@ 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 { + return nil + } return &f } - // FilledFormField represents a field in LiveChat filled form event. type FilledFormField struct { ID string `json:"id"` @@ -496,7 +498,6 @@ type FilledFormFieldGroupChooser struct { } `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. @@ -583,7 +584,6 @@ func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser { return &gc } - // Postback represents postback data in LiveChat message event. type Postback struct { ID string `json:"id"` diff --git a/customer/structures.go b/customer/structures.go index c0221ac..f5c1c4d 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -352,6 +352,9 @@ 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 { + return nil + } return &f } diff --git a/webhooks/structures.go b/webhooks/structures.go index 431d827..a5258b0 100644 --- a/webhooks/structures.go +++ b/webhooks/structures.go @@ -564,6 +564,7 @@ type eventSpecific struct { Text json.RawMessage `json:"text"` TextVars json.RawMessage `json:"text_vars"` Fields json.RawMessage `json:"fields"` + FormType json.RawMessage `json:"form_type"` ContentType json.RawMessage `json:"content_type"` Name json.RawMessage `json:"name"` URL json.RawMessage `json:"url"` @@ -612,10 +613,12 @@ 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 { + return nil + } return &f } - // FilledFormField represents a field in LiveChat filled form event. type FilledFormField struct { ID string `json:"id"` @@ -669,7 +672,6 @@ type FilledFormFieldGroupChooser struct { } `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. @@ -756,7 +758,6 @@ func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser { return &gc } - // Postback represents postback data in LiveChat message event. type Postback struct { ID string `json:"id"` From 62ed7b51ee994031438eecbe3285b3b5c2f1191d Mon Sep 17 00:00:00 2001 From: kacperf531 Date: Thu, 18 Apr 2024 17:44:07 +0200 Subject: [PATCH 6/7] fixup! fix issues found during review --- agent/structures.go | 2 +- customer/structures.go | 2 +- webhooks/structures.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/structures.go b/agent/structures.go index fb99e04..b76f781 100644 --- a/agent/structures.go +++ b/agent/structures.go @@ -439,7 +439,7 @@ 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 := json.Unmarshal(e.FormType, &f.FormType); err != nil && e.FormType != nil { return nil } return &f diff --git a/customer/structures.go b/customer/structures.go index f5c1c4d..4ed6587 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -352,7 +352,7 @@ 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 := json.Unmarshal(e.FormType, &f.FormType); err != nil && e.FormType != nil { return nil } return &f diff --git a/webhooks/structures.go b/webhooks/structures.go index a5258b0..16d405b 100644 --- a/webhooks/structures.go +++ b/webhooks/structures.go @@ -613,7 +613,7 @@ 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 := json.Unmarshal(e.FormType, &f.FormType); err != nil && e.FormType != nil { return nil } return &f From 79b7ab0dbac4f76e7dc095cbf20c030540c2286e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Fabja=C5=84czuk?= Date: Fri, 19 Apr 2024 11:08:18 +0200 Subject: [PATCH 7/7] check nil FormType before unmarshalling and add tests --- agent/structures.go | 2 +- agent/structures_test.go | 53 ++++++++++++++++++++++++++++++++++--- customer/structures.go | 2 +- customer/structures_test.go | 53 ++++++++++++++++++++++++++++++++++--- webhooks/structures.go | 2 +- webhooks/structures_test.go | 53 ++++++++++++++++++++++++++++++++++--- 6 files changed, 153 insertions(+), 12 deletions(-) diff --git a/agent/structures.go b/agent/structures.go index b76f781..fec87c5 100644 --- a/agent/structures.go +++ b/agent/structures.go @@ -439,7 +439,7 @@ 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 && e.FormType != nil { + if err := internal.UnmarshalOptionalRawField(e.FormType, &f.FormType); err != nil { return nil } return &f diff --git a/agent/structures_test.go b/agent/structures_test.go index 2049c6e..4b084d2 100644 --- a/agent/structures_test.go +++ b/agent/structures_test.go @@ -2,11 +2,46 @@ package agent_test import ( "encoding/json" + "fmt" "testing" "github.com/livechat/lc-sdk-go/v6/agent" ) +func TestFilledFormTypesOK(t *testing.T) { + rawFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"foo@bar.eu"}]`) + 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":"foo@bar.eu"}]`) @@ -60,16 +95,28 @@ func TestFilledFormFieldTypesOK(t *testing.T) { }) } -func FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *agent.FilledFormField { +func FilledFormFromJSON(t *testing.T, rawEvent json.RawMessage) *agent.FilledForm { t.Helper() - event := agent.Event{Type: "filled_form"} - event.Fields = rawFields + 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)) } diff --git a/customer/structures.go b/customer/structures.go index 4ed6587..d358a1f 100644 --- a/customer/structures.go +++ b/customer/structures.go @@ -352,7 +352,7 @@ 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 && e.FormType != nil { + if err := internal.UnmarshalOptionalRawField(e.FormType, &f.FormType); err != nil { return nil } return &f diff --git a/customer/structures_test.go b/customer/structures_test.go index d30741d..b9f8765 100644 --- a/customer/structures_test.go +++ b/customer/structures_test.go @@ -2,11 +2,46 @@ package customer_test import ( "encoding/json" + "fmt" "testing" "github.com/livechat/lc-sdk-go/v6/customer" ) +func TestFilledFormTypesOK(t *testing.T) { + rawFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"foo@bar.eu"}]`) + 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":"foo@bar.eu"}]`) @@ -60,16 +95,28 @@ func TestFilledFormFieldTypesOK(t *testing.T) { }) } -func FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *customer.FilledFormField { +func FilledFormFromJSON(t *testing.T, rawEvent json.RawMessage) *customer.FilledForm { t.Helper() - event := customer.Event{Type: "filled_form"} - event.Fields = rawFields + var event customer.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) *customer.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)) } diff --git a/webhooks/structures.go b/webhooks/structures.go index 16d405b..67389ee 100644 --- a/webhooks/structures.go +++ b/webhooks/structures.go @@ -613,7 +613,7 @@ 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 && e.FormType != nil { + if err := internal.UnmarshalOptionalRawField(e.FormType, &f.FormType); err != nil { return nil } return &f diff --git a/webhooks/structures_test.go b/webhooks/structures_test.go index a992ca5..39e337e 100644 --- a/webhooks/structures_test.go +++ b/webhooks/structures_test.go @@ -2,11 +2,46 @@ package webhooks_test import ( "encoding/json" + "fmt" "testing" "github.com/livechat/lc-sdk-go/v6/webhooks" ) +func TestFilledFormTypesOK(t *testing.T) { + rawFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"foo@bar.eu"}]`) + 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":"foo@bar.eu"}]`) @@ -60,16 +95,28 @@ func TestFilledFormFieldTypesOK(t *testing.T) { }) } -func FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *webhooks.FilledFormField { +func FilledFormFromJSON(t *testing.T, rawEvent json.RawMessage) *webhooks.FilledForm { t.Helper() - event := webhooks.Event{Type: "filled_form"} - event.Fields = rawFields + var event webhooks.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) *webhooks.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)) }