|
| 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 | +} |
0 commit comments