Skip to content

Commit f2f67b9

Browse files
archandattaclaude
andcommitted
test: add e2e read path for GET /telemetry/events
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent f51d9ff commit f2f67b9

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"os"
7+
"os/exec"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
14+
instanceoapi "github.com/kernel/kernel-images/server/lib/oapi"
15+
)
16+
17+
// TestReadTelemetryEvents starts a headless container with S2 credentials,
18+
// publishes a known set of events, and reads them back through
19+
// GET /telemetry/events. It exercises the full archive read path against a real
20+
// S2 stream rather than the in-memory ring buffer.
21+
//
22+
// Skips automatically when S2_BASIN, S2_ACCESS_TOKEN, or S2_STREAM are unset.
23+
func TestReadTelemetryEvents(t *testing.T) {
24+
basin := os.Getenv("S2_BASIN")
25+
accessToken := os.Getenv("S2_ACCESS_TOKEN")
26+
stream := os.Getenv("S2_STREAM")
27+
if basin == "" || accessToken == "" || stream == "" {
28+
t.Skip("S2_BASIN, S2_ACCESS_TOKEN, and S2_STREAM must be set to run this test")
29+
}
30+
31+
if _, err := exec.LookPath("docker"); err != nil {
32+
t.Skipf("docker not available: %v", err)
33+
}
34+
35+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
36+
defer cancel()
37+
38+
c := NewTestContainer(t, headlessImage)
39+
require.NoError(t, c.Start(ctx, ContainerConfig{
40+
Env: map[string]string{
41+
"S2_BASIN": basin,
42+
"S2_ACCESS_TOKEN": accessToken,
43+
"S2_STREAM": stream,
44+
},
45+
}), "failed to start container")
46+
defer c.Stop(ctx)
47+
48+
require.NoError(t, c.WaitReady(ctx), "api not ready")
49+
50+
client, err := c.APIClient()
51+
require.NoError(t, err)
52+
53+
// Start a telemetry session. The default config enables the system and
54+
// connection categories, which is what we publish into below.
55+
startResp, err := client.PutTelemetryWithResponse(ctx, instanceoapi.PutTelemetryJSONRequestBody{})
56+
require.NoError(t, err)
57+
require.Equal(t, http.StatusCreated, startResp.StatusCode(), "put telemetry: %s", string(startResp.Body))
58+
59+
// Publish a deterministic set of events across two enabled categories.
60+
const systemCount, connectionCount = 3, 2
61+
for i := 0; i < systemCount; i++ {
62+
publishEvent(t, ctx, client, "test.system", instanceoapi.PublishEventRequestCategorySystem)
63+
}
64+
for i := 0; i < connectionCount; i++ {
65+
publishEvent(t, ctx, client, "test.connection", instanceoapi.PublishEventRequestCategoryConnection)
66+
}
67+
68+
// Give the storage writer time to flush to S2 (batcher linger + network).
69+
time.Sleep(2 * time.Second)
70+
71+
// Bound every read tightly: a correct handler caps the S2 read at the tail,
72+
// so these return promptly. A hang here means the read is unbounded.
73+
readCtx, readCancel := context.WithTimeout(ctx, 10*time.Second)
74+
defer readCancel()
75+
76+
// Full read returns at least everything we published.
77+
all, err := client.ReadTelemetryEventsWithResponse(readCtx, &instanceoapi.ReadTelemetryEventsParams{})
78+
require.NoError(t, err)
79+
require.Equal(t, http.StatusOK, all.StatusCode(), "read events: %s", string(all.Body))
80+
require.NotNil(t, all.JSON200)
81+
assert.GreaterOrEqual(t, len(all.JSON200.Events), systemCount+connectionCount)
82+
83+
// Category filter returns only the requested category.
84+
systemCat := []instanceoapi.TelemetryEventCategory{instanceoapi.TelemetryEventCategorySystem}
85+
systemOnly, err := client.ReadTelemetryEventsWithResponse(readCtx, &instanceoapi.ReadTelemetryEventsParams{Category: &systemCat})
86+
require.NoError(t, err)
87+
require.Equal(t, http.StatusOK, systemOnly.StatusCode())
88+
require.NotNil(t, systemOnly.JSON200)
89+
assert.GreaterOrEqual(t, len(systemOnly.JSON200.Events), systemCount)
90+
for _, e := range systemOnly.JSON200.Events {
91+
require.NotNil(t, e.Event.Category)
92+
assert.Equal(t, instanceoapi.TelemetryEventCategorySystem, *e.Event.Category)
93+
}
94+
95+
// Limit caps the number of returned events.
96+
limit := 1
97+
limited, err := client.ReadTelemetryEventsWithResponse(readCtx, &instanceoapi.ReadTelemetryEventsParams{Limit: &limit})
98+
require.NoError(t, err)
99+
require.NotNil(t, limited.JSON200)
100+
assert.Len(t, limited.JSON200.Events, 1)
101+
102+
// An empty window returns [] not null, or the Python SDK chokes deserializing.
103+
pastSince, pastUntil := int64(1), int64(2)
104+
empty, err := client.ReadTelemetryEventsWithResponse(readCtx, &instanceoapi.ReadTelemetryEventsParams{Since: &pastSince, Until: &pastUntil})
105+
require.NoError(t, err)
106+
require.NotNil(t, empty.JSON200)
107+
assert.Empty(t, empty.JSON200.Events)
108+
assert.Contains(t, string(empty.Body), `"events":[]`)
109+
}
110+
111+
func publishEvent(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, eventType string, category instanceoapi.PublishEventRequestCategory) {
112+
t.Helper()
113+
resp, err := client.PublishTelemetryEventWithResponse(ctx, instanceoapi.PublishTelemetryEventJSONRequestBody{
114+
Type: eventType,
115+
Category: &category,
116+
})
117+
require.NoError(t, err)
118+
require.Equal(t, http.StatusOK, resp.StatusCode(), "publish %s: %s", eventType, string(resp.Body))
119+
}

0 commit comments

Comments
 (0)