Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions flashduty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,168 @@ func TestDoReturnsRateLimitErrorOn429(t *testing.T) {
t.Fatalf("Response.RateLimit not populated: %+v", resp.RateLimit)
}
}

// lookup walks a decoded JSON body along path and reports the value at the end
// of it, plus whether every segment existed.
func lookup(payload map[string]any, path ...string) (any, bool) {
var cur any = payload
for _, key := range path {
obj, ok := cur.(map[string]any)
if !ok {
return nil, false
}
cur, ok = obj[key]
if !ok {
return nil, false
}
}
return cur, true
}

// Fields whose zero value carries meaning are generated as pointers so the
// caller can send it. Sending the zero value must put the key on the wire.
func TestNullableRequestFieldsSendExplicitZero(t *testing.T) {
c, _ := NewClient("KEY", WithBaseURL("https://api.flashcat.cloud"), WithLogger(noopLogger{}))

tests := []struct {
name string
path string
body any
at []string
want any
}{
{
name: "rum application update clears is_private",
path: "/rum/application/update",
body: &RUMApplicationUpdateRequest{ApplicationID: "app-1", IsPrivate: Bool(false)},
at: []string{"is_private"},
want: false,
},
{
name: "rum application update re-enables geo inference",
path: "/rum/application/update",
body: &RUMApplicationUpdateRequest{ApplicationID: "app-1", NoGeo: Bool(false)},
at: []string{"no_geo"},
want: false,
},
{
name: "rum application update re-enables ip collection",
path: "/rum/application/update",
body: &RUMApplicationUpdateRequest{ApplicationID: "app-1", NoIP: Bool(false)},
at: []string{"no_ip"},
want: false,
},
{
// A minimal alerting override is entirely zero-valued apart from
// Enabled; the pointer keeps the omitzero container on the wire.
name: "rum application update disables alerting",
path: "/rum/application/update",
body: &RUMApplicationUpdateRequest{ApplicationID: "app-1", Alerting: RUMApplicationAlerting{Enabled: Bool(false)}},
at: []string{"alerting", "enabled"},
want: false,
},
{
name: "rum field list selects non-facet fields",
path: "/rum/field/list",
body: &RUMFieldListRequest{IsFacet: Bool(false)},
at: []string{"is_facet"},
want: false,
},
{
name: "schedule notifies exactly at shift start",
path: "/schedule/create",
body: &ScheduleUpsertRequest{Notify: ScheduleNotify{AdvanceInTime: Int64(0)}},
at: []string{"notify", "advance_in_time"},
want: float64(0),
},
{
name: "template update turns the feishu card table off",
path: "/template/update",
body: &TemplateUpdateRequest{TemplateID: "t-1", TemplateName: "t", FeishuAppCardV2TableEnabled: Bool(false)},
at: []string{"feishu_app_card_v2_table_enabled"},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := c.newRequest(context.Background(), http.MethodPost, tt.path, tt.body)
if err != nil {
t.Fatal(err)
}
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatal(err)
}
got, ok := lookup(payload, tt.at...)
if !ok {
t.Fatalf("%s missing from request body: %s", strings.Join(tt.at, "."), body)
}
if got != tt.want {
t.Fatalf("%s = %#v, want %#v, body = %s", strings.Join(tt.at, "."), got, tt.want, body)
}
})
}
}

// A nil pointer must stay off the wire so the server leaves the field alone.
func TestNullableRequestFieldsOmitUnsetValues(t *testing.T) {
c, _ := NewClient("KEY", WithBaseURL("https://api.flashcat.cloud"), WithLogger(noopLogger{}))

tests := []struct {
name string
path string
body any
absent []string
present []string
}{
{
name: "rum application update leaves privacy toggles alone",
path: "/rum/application/update",
body: &RUMApplicationUpdateRequest{ApplicationID: "app-1", ApplicationName: "renamed"},
absent: []string{`"is_private"`, `"no_geo"`, `"no_ip"`, `"alerting"`},
present: []string{`"application_name":"renamed"`},
},
{
name: "rum field list returns every field",
path: "/rum/field/list",
body: &RUMFieldListRequest{Scopes: []string{"session"}},
absent: []string{`"is_facet"`},
present: []string{`"scopes"`},
},
{
name: "template update leaves the feishu card table alone",
path: "/template/update",
body: &TemplateUpdateRequest{TemplateID: "t-1", TemplateName: "t"},
absent: []string{`"feishu_app_card_v2_table_enabled"`},
present: []string{`"template_id":"t-1"`},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := c.newRequest(context.Background(), http.MethodPost, tt.path, tt.body)
if err != nil {
t.Fatal(err)
}
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
for _, key := range tt.absent {
if strings.Contains(string(body), key) {
t.Fatalf("unset %s must be omitted from the wire, got body = %s", key, body)
}
}
for _, key := range tt.present {
if !strings.Contains(string(body), key) {
t.Fatalf("%s missing from request body: %s", key, body)
}
}
})
}
}
30 changes: 15 additions & 15 deletions models_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 33 additions & 15 deletions openapi/openapi.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38514,9 +38514,12 @@
],
"properties": {
"advance_in_time": {
"type": "integer",
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Advance notification lead time (seconds)."
"description": "Advance notification lead time in seconds. `0` notifies exactly at shift start; omitting disables advance notification."
},
"fixed_time": {
"$ref": "#/components/schemas/ScheduleFixedTimeNotifyInfo"
Expand Down Expand Up @@ -39462,7 +39465,7 @@
"wecom",
"feishu",
"feishu_app",
"feishu_app_card_table_enabled",
"feishu_app_card_v2_table_enabled",
"dingtalk_app",
"wecom_app",
"slack_app",
Expand Down Expand Up @@ -39529,7 +39532,7 @@
"type": "string",
"description": "Feishu app message template source."
},
"feishu_app_card_table_enabled": {
"feishu_app_card_v2_table_enabled": {
"type": "boolean",
"description": "Whether alert labels use table rendering in Feishu app cards."
},
Expand Down Expand Up @@ -39754,7 +39757,7 @@
"type": "string",
"description": "Feishu app message template source."
},
"feishu_app_card_table_enabled": {
"feishu_app_card_v2_table_enabled": {
"type": "boolean",
"default": false,
"description": "Render alert labels as a table in Feishu app cards."
Expand Down Expand Up @@ -39872,7 +39875,7 @@
"type": "string",
"description": "Feishu app message template source."
},
"feishu_app_card_table_enabled": {
"feishu_app_card_v2_table_enabled": {
"type": [
"boolean",
"null"
Expand Down Expand Up @@ -44520,7 +44523,10 @@
"description": "Alert settings for the application.",
"properties": {
"enabled": {
"type": "boolean",
"type": [
"boolean",
"null"
],
"description": "Whether alerting is enabled."
},
"channel_ids": {
Expand Down Expand Up @@ -44901,16 +44907,25 @@
"description": "Owning team ID. Get team IDs via `POST /team/list`. Omit to leave unchanged."
},
"is_private": {
"type": "boolean",
"description": "Restrict access to members of the owning team. Omit to leave unchanged."
"type": [
"boolean",
"null"
],
"description": "Restrict access to members of the owning team; `false` explicitly makes the application public. Omit to leave unchanged."
},
"no_ip": {
"type": "boolean",
"description": "When `true`, stop collecting user IP addresses. Omit to leave unchanged."
"type": [
"boolean",
"null"
],
"description": "When `true`, stop collecting user IP addresses; when `false`, resume collecting them. Omit to leave unchanged."
},
"no_geo": {
"type": "boolean",
"description": "When `true`, stop inferring geographic location from IP. Omit to leave unchanged."
"type": [
"boolean",
"null"
],
"description": "When `true`, stop inferring geographic location from IP; when `false`, resume inferring it. Omit to leave unchanged."
},
"alerting": {
"$ref": "#/components/schemas/RumApplicationAlerting",
Expand Down Expand Up @@ -49781,8 +49796,11 @@
"description": "Filter by RUM data scopes. Valid values: `session`, `view`, `action`, `error`, `resource`, `long_task`, `vital`, `issue`, `sourcemap`."
},
"is_facet": {
"type": "boolean",
"description": "When true, return only facet-enabled fields. When false or omitted, return all fields."
"type": [
"boolean",
"null"
],
"description": "When omitted or `null`, return all fields. When `true`, return only facet-enabled fields. When `false`, return only fields that are not facet-enabled."
}
}
},
Expand Down
Loading