Skip to content

Commit 1d242c4

Browse files
test(coverage): ResourceLogs stream_failed arm (logs.go:230-236)
Wrap the k8s fake clientset so pod GetLogs returns a request whose Stream(ctx) errors (rest/fake.RESTClient with Err set), driving the last uncovered ResourceLogs arm to 100%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cd01329 commit 1d242c4

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package handlers_test
2+
3+
// logs_streamerr_twinlogs_test.go — covers the LAST uncovered arm of
4+
// LogsHandler.ResourceLogs (logs.go:230-236): req.Stream(streamCtx) returns an
5+
// error, so the handler logs stream_failed, cancels the background context, and
6+
// returns 503 stream_failed.
7+
//
8+
// The vanilla k8s fake clientset's GetLogs always returns a request whose
9+
// Stream succeeds with a canned "fake logs" body, so the error arm is
10+
// unreachable through it. We wrap the fake in a thin kubernetes.Interface that
11+
// delegates everything (so pod LIST still succeeds and we reach the GetLogs
12+
// step) EXCEPT pod GetLogs, which we override to return a request backed by a
13+
// rest/fake.RESTClient whose Err is set — making Stream(ctx) fail
14+
// deterministically.
15+
16+
import (
17+
"net/http"
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
corev1 "k8s.io/api/core/v1"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/runtime/schema"
24+
"k8s.io/client-go/kubernetes"
25+
k8sfake "k8s.io/client-go/kubernetes/fake"
26+
"k8s.io/client-go/kubernetes/scheme"
27+
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
28+
restclient "k8s.io/client-go/rest"
29+
restfake "k8s.io/client-go/rest/fake"
30+
31+
"instant.dev/internal/handlers"
32+
"instant.dev/internal/testhelpers"
33+
34+
"errors"
35+
)
36+
37+
// streamErrClientset wraps a real fake clientset; only pod GetLogs is altered so
38+
// its returned request fails on Stream.
39+
type streamErrClientset struct {
40+
kubernetes.Interface
41+
}
42+
43+
func (c *streamErrClientset) CoreV1() typedcorev1.CoreV1Interface {
44+
return &streamErrCoreV1{c.Interface.CoreV1()}
45+
}
46+
47+
type streamErrCoreV1 struct {
48+
typedcorev1.CoreV1Interface
49+
}
50+
51+
func (c *streamErrCoreV1) Pods(namespace string) typedcorev1.PodInterface {
52+
return &streamErrPods{c.CoreV1Interface.Pods(namespace)}
53+
}
54+
55+
type streamErrPods struct {
56+
typedcorev1.PodInterface
57+
}
58+
59+
// GetLogs returns a request whose Stream(ctx) errors — backed by a
60+
// rest/fake.RESTClient with Err set.
61+
func (p *streamErrPods) GetLogs(name string, opts *corev1.PodLogOptions) *restclient.Request {
62+
rc := &restfake.RESTClient{
63+
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
64+
GroupVersion: schema.GroupVersion{Version: "v1"},
65+
Err: errors.New("log stream upstream unavailable"),
66+
}
67+
return rc.Request()
68+
}
69+
70+
func TestLogs_StreamFailed_503(t *testing.T) {
71+
db, clean := testhelpers.SetupTestDB(t)
72+
defer clean()
73+
74+
const ns = "ns-stream-err"
75+
// A matching pod so the LIST step succeeds and we reach GetLogs/Stream.
76+
base := k8sfake.NewSimpleClientset(&corev1.Pod{
77+
ObjectMeta: metav1.ObjectMeta{
78+
Name: "postgres-0",
79+
Namespace: ns,
80+
Labels: map[string]string{"app": "postgres"},
81+
},
82+
})
83+
cs := &streamErrClientset{Interface: base}
84+
85+
h := handlers.NewLogsHandler(db)
86+
h.SetClientset(cs)
87+
app := logsTestApp(t, db, h)
88+
89+
token := seedLogsResource(t, db, "postgres", "growth", "active", ns)
90+
resp := logsGet(t, app, token, "")
91+
defer resp.Body.Close()
92+
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
93+
}

0 commit comments

Comments
 (0)