Skip to content

Commit e92aa49

Browse files
committed
test(e2e): sweep all 21 services live + second (calendar) write lifecycle
Addresses thin E2E coverage: previously only one team write was exercised. - sweep_test.go: TestServiceReadSweep hits one representative read on every one of the 21 generated services against the live API; a nil error proves the request encoded and the real response decoded into the typed model. Each service is an isolated subtest. Endpoints that require deployment-specific context with no generic way to obtain it (RuleSets type_ident, StatusPages page_id — no list-pages endpoint) are skipped with the server's reason via the IsInvalidParameter predicate, never silently passed. Result: 19 pass, 2 skip. - write_test.go: add TestCalendarLifecycle (create -> read -> update -> delete, self-cleaning) so a second resource proves the write path generalizes beyond teams. Note: RUM and webhook-history endpoints take millisecond timestamps while the core platform takes seconds — captured in the sweep so future readers see it.
1 parent b283319 commit e92aa49

2 files changed

Lines changed: 231 additions & 0 deletions

File tree

‎e2e/sweep_test.go‎

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//go:build e2e
2+
3+
package e2e
4+
5+
import (
6+
"context"
7+
"testing"
8+
"time"
9+
10+
flashduty "github.com/flashcatcloud/go-flashduty"
11+
)
12+
13+
// TestServiceReadSweep exercises at least one read endpoint on every one of the
14+
// 21 generated services against the live API, proving that each service's
15+
// request encodes, the round-trip succeeds, and the real response decodes into
16+
// the SDK's typed model (a nil error from do()/doGet() means json.Unmarshal into
17+
// the typed response struct succeeded).
18+
//
19+
// Each service is an independent subtest so one failure does not mask the rest.
20+
// The generic *Response envelope carries request_id and the list total, so the
21+
// sweep logs those uniformly without depending on each response's field names —
22+
// per-field usability is covered by the README example (example_test.go), the
23+
// incident reads, and the team write lifecycle.
24+
func TestServiceReadSweep(t *testing.T) {
25+
c := newClient(t)
26+
ctx := context.Background()
27+
now := time.Now().Unix()
28+
weekAgo := now - 7*24*3600
29+
// RUM and webhook-history endpoints take millisecond timestamps, unlike the
30+
// core platform (incidents/alerts/analytics) which takes seconds.
31+
nowMs := now * 1000
32+
weekAgoMs := weekAgo * 1000
33+
34+
type readFn func() (requestID string, total int, err error)
35+
36+
// Endpoints that require a pre-existing resource id the sweep cannot obtain
37+
// generically (e.g. a status-page id — this API surface has no list-pages
38+
// endpoint). These are skipped with the server's reason when the server
39+
// rejects on InvalidParameter, rather than failed — never silently passed.
40+
needsResource := map[string]bool{"StatusPages": true, "RuleSets": true}
41+
42+
// One representative read per service. Listed in client field order.
43+
reads := []struct {
44+
service string
45+
run readFn
46+
}{
47+
{"Members", func() (string, int, error) {
48+
_, r, err := c.Members.MemberInfo(ctx)
49+
return rid(r), tot(r), err
50+
}},
51+
{"Teams", func() (string, int, error) {
52+
_, r, err := c.Teams.ReadList(ctx, &flashduty.TeamListRequest{})
53+
return rid(r), tot(r), err
54+
}},
55+
{"Channels", func() (string, int, error) {
56+
_, r, err := c.Channels.ChannelList(ctx, &flashduty.ListChannelsRequest{})
57+
return rid(r), tot(r), err
58+
}},
59+
{"Incidents", func() (string, int, error) {
60+
_, r, err := c.Incidents.List(ctx, &flashduty.ListIncidentsRequest{
61+
StartTime: weekAgo, EndTime: now, ListOptions: flashduty.ListOptions{Limit: 3},
62+
})
63+
return rid(r), tot(r), err
64+
}},
65+
{"Alerts", func() (string, int, error) {
66+
_, r, err := c.Alerts.ReadList(ctx, &flashduty.AlertListRequest{
67+
StartTime: weekAgo, EndTime: now, ListOptions: flashduty.ListOptions{Limit: 3},
68+
})
69+
return rid(r), tot(r), err
70+
}},
71+
{"AlertRules", func() (string, int, error) {
72+
// ReadList needs a folder context; this param-less counter read also
73+
// exercises the map[string]int64 response type fixed in v0.2.0.
74+
_, r, err := c.AlertRules.ReadCounterChannel(ctx)
75+
return rid(r), tot(r), err
76+
}},
77+
{"AlertEnrichment", func() (string, int, error) {
78+
// EnrichmentReadList requires integration ids; FieldReadList is the
79+
// account-level, parameter-free read for this service.
80+
_, r, err := c.AlertEnrichment.FieldReadList(ctx, &flashduty.FieldListRequest{})
81+
return rid(r), tot(r), err
82+
}},
83+
{"DataSources", func() (string, int, error) {
84+
_, r, err := c.DataSources.ReadList(ctx, &flashduty.DataSourceListRequest{})
85+
return rid(r), tot(r), err
86+
}},
87+
{"Integrations", func() (string, int, error) {
88+
_, r, err := c.Integrations.List(ctx, &flashduty.ListWebhookHistoryRequest{
89+
StartTime: weekAgoMs, EndTime: nowMs, Limit: 3,
90+
})
91+
return rid(r), tot(r), err
92+
}},
93+
{"NotificationTemplates", func() (string, int, error) {
94+
_, r, err := c.NotificationTemplates.ReadList(ctx, &flashduty.TemplateListRequest{})
95+
return rid(r), tot(r), err
96+
}},
97+
{"RolesPermissions", func() (string, int, error) {
98+
_, r, err := c.RolesPermissions.ReadList(ctx, &flashduty.RoleListRequest{})
99+
return rid(r), tot(r), err
100+
}},
101+
{"RuleSets", func() (string, int, error) {
102+
_, r, err := c.RuleSets.List(ctx, &flashduty.StoreRulesetListRequest{TypeIdent: "prometheus"})
103+
return rid(r), tot(r), err
104+
}},
105+
{"Schedules", func() (string, int, error) {
106+
_, r, err := c.Schedules.List(ctx, &flashduty.ScheduleListRequest{})
107+
return rid(r), tot(r), err
108+
}},
109+
{"Calendars", func() (string, int, error) {
110+
_, r, err := c.Calendars.CalendarList(ctx, &flashduty.CalendarListRequest{})
111+
return rid(r), tot(r), err
112+
}},
113+
{"AuditLogs", func() (string, int, error) {
114+
_, r, err := c.AuditLogs.OperationList(ctx)
115+
return rid(r), tot(r), err
116+
}},
117+
{"Analytics", func() (string, int, error) {
118+
_, r, err := c.Analytics.IncidentList(ctx, &flashduty.InsightIncidentListRequest{
119+
StartTime: weekAgo, EndTime: now,
120+
})
121+
return rid(r), tot(r), err
122+
}},
123+
{"StatusPages", func() (string, int, error) {
124+
_, r, err := c.StatusPages.ChangeList(ctx, &flashduty.StatusPagesChangeListRequest{})
125+
return rid(r), tot(r), err
126+
}},
127+
{"Applications", func() (string, int, error) {
128+
_, r, err := c.Applications.ReadList(ctx, &flashduty.RUMApplicationListRequest{})
129+
return rid(r), tot(r), err
130+
}},
131+
{"Issues", func() (string, int, error) {
132+
_, r, err := c.Issues.ReadList(ctx, &flashduty.RUMIssueListRequest{
133+
StartTime: weekAgoMs, EndTime: nowMs,
134+
})
135+
return rid(r), tot(r), err
136+
}},
137+
{"Sourcemaps", func() (string, int, error) {
138+
_, r, err := c.Sourcemaps.List(ctx, &flashduty.SourcemapListRequest{
139+
StartTime: weekAgoMs, EndTime: nowMs,
140+
})
141+
return rid(r), tot(r), err
142+
}},
143+
{"Diagnostics", func() (string, int, error) {
144+
_, r, err := c.Diagnostics.TargetsList(ctx, &flashduty.TargetsListRequest{})
145+
return rid(r), tot(r), err
146+
}},
147+
}
148+
149+
for _, rd := range reads {
150+
t.Run(rd.service, func(t *testing.T) {
151+
requestID, total, err := rd.run()
152+
if err != nil {
153+
if needsResource[rd.service] && flashduty.IsInvalidParameter(err) {
154+
t.Skipf("requires deployment-specific context (a resource id or valid type identifier) the sweep cannot synthesize: %v", err)
155+
}
156+
t.Fatalf("read failed: %v", err)
157+
}
158+
t.Logf("ok request_id=%s total=%d", requestID, total)
159+
})
160+
}
161+
}
162+
163+
// rid/tot read the generic envelope fields off any service method's *Response,
164+
// tolerating a nil response (e.g. when an error occurred before the round-trip).
165+
func rid(r *flashduty.Response) string {
166+
if r == nil {
167+
return ""
168+
}
169+
return r.RequestID
170+
}
171+
172+
func tot(r *flashduty.Response) int {
173+
if r == nil {
174+
return 0
175+
}
176+
return r.Total
177+
}

‎e2e/write_test.go‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,57 @@ func TestTeamLifecycle(t *testing.T) {
5959
t.Errorf("description = %q, want %q", updated.Description, updatedDesc)
6060
}
6161
}
62+
63+
// TestCalendarLifecycle creates a calendar, reads it, updates it, and deletes
64+
// it — a second write-path resource (beyond teams) proving create/update/delete
65+
// generalize across services. It only ever touches the "gofd-e2e-" calendar it
66+
// creates and always deletes it on cleanup.
67+
func TestCalendarLifecycle(t *testing.T) {
68+
c := newClient(t)
69+
ctx := context.Background()
70+
71+
name := uniqueName("cal")
72+
created, _, err := c.Calendars.CalendarCreate(ctx, &flashduty.CalendarCreateRequest{
73+
CalName: name,
74+
Timezone: "Asia/Shanghai",
75+
Workdays: []int64{1, 2, 3, 4, 5},
76+
})
77+
if err != nil {
78+
t.Fatalf("create calendar: %v", err)
79+
}
80+
id := created.CalID
81+
if id == "" {
82+
t.Fatalf("create calendar returned no cal_id")
83+
}
84+
t.Cleanup(func() {
85+
if _, err := c.Calendars.CalendarDelete(ctx, &flashduty.CalendarIDRequest{CalID: id}); err != nil {
86+
t.Errorf("cleanup: delete calendar %s: %v", id, err)
87+
}
88+
})
89+
90+
got, _, err := c.Calendars.CalendarInfo(ctx, &flashduty.CalendarIDRequest{CalID: id})
91+
if err != nil {
92+
t.Fatalf("read calendar: %v", err)
93+
}
94+
if got.CalName != name {
95+
t.Errorf("calendar name = %q, want %q", got.CalName, name)
96+
}
97+
98+
const updatedDesc = "go-flashduty e2e test calendar (updated)"
99+
if _, err := c.Calendars.CalendarUpdate(ctx, &flashduty.CalendarUpdateRequest{
100+
CalID: id,
101+
CalName: name,
102+
Timezone: "Asia/Shanghai",
103+
Description: updatedDesc,
104+
}); err != nil {
105+
t.Fatalf("update calendar: %v", err)
106+
}
107+
108+
updated, _, err := c.Calendars.CalendarInfo(ctx, &flashduty.CalendarIDRequest{CalID: id})
109+
if err != nil {
110+
t.Fatalf("read updated calendar: %v", err)
111+
}
112+
if updated.Description != updatedDesc {
113+
t.Errorf("description = %q, want %q", updated.Description, updatedDesc)
114+
}
115+
}

0 commit comments

Comments
 (0)