Skip to content

Commit 805859c

Browse files
steveiliop56codex
andcommitted
tests: add tests for kube service and extractors
Co-Authored-By: Codex <noreply@openai.com>
1 parent 52661a9 commit 805859c

3 files changed

Lines changed: 444 additions & 280 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package service
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/tinyauthapp/tinyauth/internal/model"
8+
"github.com/tinyauthapp/tinyauth/pkg/apis/tinyauth/v1alpha1"
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
clientfake "k8s.io/client-go/kubernetes/fake"
12+
)
13+
14+
func TestKubernetesCRDExtractorExtract(t *testing.T) {
15+
meta := &ResourceMeta{Typ: ResourceTypeCRD, Name: "dashboard", Namespace: "default"}
16+
base := testApplication("dashboard", "dashboard.example.com")
17+
base.Spec.Users.Allow = "alice"
18+
base.Spec.OAuth.Groups = "admins"
19+
base.Spec.IP.Allow = []string{"192.0.2.0/24"}
20+
base.Spec.Path.Block = "/private"
21+
base.Spec.Response.BasicAuth.Username = "viewer"
22+
withRef := base.DeepCopy()
23+
withRef.Spec.Response.BasicAuth.PasswordSecretRef = &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: "credentials"}, Key: "password"}
24+
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "credentials", Namespace: "default"}, Data: map[string][]byte{"password": []byte("s3cret")}}
25+
missingKey := secret.DeepCopy()
26+
missingKey.Data = map[string][]byte{"other": []byte("s3cret")}
27+
app := model.App{
28+
Config: model.AppConfig{Domain: "dashboard.example.com"},
29+
Users: model.AppUsers{Allow: "alice"},
30+
OAuth: model.AppOAuth{Groups: "admins"},
31+
IP: model.AppIP{Allow: []string{"192.0.2.0/24"}},
32+
Path: model.AppPath{Block: "/private"},
33+
Response: model.AppResponse{BasicAuth: model.AppBasicAuth{Username: "viewer"}},
34+
}
35+
withPassword := app
36+
withPassword.Response.BasicAuth.Password = "s3cret"
37+
tests := []struct {
38+
name string
39+
resource *corev1.Secret
40+
application func() *v1alpha1.Application
41+
want ExtractionResult
42+
reads int
43+
}{
44+
{"valid application", nil, func() *v1alpha1.Application { return base.DeepCopy() }, ExtractionResult{Meta: meta, Apps: map[string]model.App{"dashboard": app}}, 0},
45+
{"password from secret", secret, func() *v1alpha1.Application { return withRef.DeepCopy() }, ExtractionResult{Meta: meta, Apps: map[string]model.App{"dashboard": withPassword}}, 1},
46+
{"secret not found", nil, func() *v1alpha1.Application { return withRef.DeepCopy() }, ExtractionResult{Meta: meta}, 1},
47+
{"secret key not found", missingKey, func() *v1alpha1.Application { return withRef.DeepCopy() }, ExtractionResult{Meta: meta}, 1},
48+
{"missing domain", nil, func() *v1alpha1.Application { a := base.DeepCopy(); a.Spec.Config.Domain = ""; return a }, ExtractionResult{Meta: meta}, 0},
49+
{"non-ascii domain", nil, func() *v1alpha1.Application {
50+
a := base.DeepCopy()
51+
a.Spec.Config.Domain = "dömain.example.com"
52+
return a
53+
}, ExtractionResult{Meta: meta}, 0},
54+
{"missing name", nil, func() *v1alpha1.Application { a := base.DeepCopy(); a.Name = ""; return a }, ExtractionResult{}, 0},
55+
{"missing namespace", nil, func() *v1alpha1.Application { a := base.DeepCopy(); a.Namespace = ""; return a }, ExtractionResult{}, 0},
56+
}
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
client := clientfake.NewClientset()
60+
if tt.resource != nil {
61+
client = clientfake.NewClientset(tt.resource)
62+
}
63+
extractor := NewKubernetesCRDExtractor(KubernetesCRDInput{Log: kubernetesTestLogger(), Client: client})
64+
assert.Equal(t, tt.want, extractor.Extract(tt.application()))
65+
assert.Len(t, client.Actions(), tt.reads)
66+
})
67+
}
68+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package service
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/tinyauthapp/tinyauth/internal/model"
8+
networking "k8s.io/api/networking/v1"
9+
)
10+
11+
func TestKubernetesIngressExtractorExtract(t *testing.T) {
12+
meta := &ResourceMeta{Typ: ResourceTypeIngress, Name: "route", Namespace: "default"}
13+
tests := []struct {
14+
name string
15+
ingress *networking.Ingress
16+
want ExtractionResult
17+
}{
18+
{
19+
name: "domain, name and wildcard match across hosts",
20+
ingress: testIngress("route", map[string]string{
21+
"tinyauth.apps.dashboard.config.domain": "app.example.com",
22+
"tinyauth.apps.dashboard.users.allow": "alice",
23+
"tinyauth.apps.portal.users.allow": "bob",
24+
"tinyauth.apps.other.users.allow": "carol",
25+
}, "app.example.com", "Portal.example.com"),
26+
want: ExtractionResult{Meta: meta, Apps: map[string]model.App{
27+
"dashboard": {Config: model.AppConfig{Domain: "app.example.com"}, Users: model.AppUsers{Allow: "alice"}},
28+
"portal": {Users: model.AppUsers{Allow: "bob"}},
29+
}},
30+
},
31+
{
32+
name: "wildcard matches configured domain",
33+
ingress: testIngress("route", map[string]string{
34+
"tinyauth.apps.dashboard.config.domain": "dashboard.example.com",
35+
}, "*.example.com"),
36+
want: ExtractionResult{Meta: meta, Apps: map[string]model.App{
37+
"dashboard": {Config: model.AppConfig{Domain: "dashboard.example.com"}},
38+
}},
39+
},
40+
{
41+
name: "hostless rule matches name",
42+
ingress: testIngress("route", map[string]string{"tinyauth.apps.dashboard.users.allow": "alice"}, ""),
43+
want: ExtractionResult{Meta: meta, Apps: map[string]model.App{
44+
"dashboard": {Users: model.AppUsers{Allow: "alice"}},
45+
}},
46+
},
47+
{
48+
name: "invalid domain falls back to name",
49+
ingress: testIngress("route", map[string]string{"tinyauth.apps.dashboard.config.domain": "dömain.example.com"}, "dashboard.example.com"),
50+
want: ExtractionResult{Meta: meta, Apps: map[string]model.App{
51+
"dashboard": {Config: model.AppConfig{Domain: "dömain.example.com"}},
52+
}},
53+
},
54+
{
55+
name: "unmatched annotations yield empty apps",
56+
ingress: testIngress("route", map[string]string{"tinyauth.apps.other.users.allow": "alice"}, "dashboard.example.com"),
57+
want: ExtractionResult{Meta: meta, Apps: map[string]model.App{}},
58+
},
59+
{
60+
name: "no annotations yield empty apps",
61+
ingress: testIngress("route", nil, "dashboard.example.com"),
62+
want: ExtractionResult{Meta: meta, Apps: map[string]model.App{}},
63+
},
64+
{
65+
name: "invalid annotations",
66+
ingress: testIngress("route", map[string]string{"tinyauth.apps.dashboard.users.invalid": "alice"}, "dashboard.example.com"),
67+
want: ExtractionResult{Meta: meta},
68+
},
69+
{
70+
name: "no rules",
71+
ingress: testIngress("route", nil),
72+
want: ExtractionResult{Meta: meta},
73+
},
74+
{
75+
name: "missing name",
76+
ingress: testIngress("", nil, "app.example.com"),
77+
want: ExtractionResult{},
78+
},
79+
{
80+
name: "missing namespace",
81+
ingress: &networking.Ingress{},
82+
want: ExtractionResult{},
83+
},
84+
}
85+
extractor := NewKubernetesIngressExtractor(KubernetesIngressExtractorInput{Log: kubernetesTestLogger()})
86+
for _, tt := range tests {
87+
t.Run(tt.name, func(t *testing.T) {
88+
assert.Equal(t, tt.want, extractor.Extract(tt.ingress))
89+
})
90+
}
91+
}
92+
93+
func TestKubernetesIngressExtractorHostsAndPaths(t *testing.T) {
94+
extractor := NewKubernetesIngressExtractor(KubernetesIngressExtractorInput{Log: kubernetesTestLogger()})
95+
for _, tt := range []struct {
96+
name string
97+
rules []networking.IngressRule
98+
hosts []string
99+
paths []string
100+
}{
101+
{"no rules", nil, nil, nil},
102+
{"no HTTP paths", []networking.IngressRule{{Host: "app.example.com"}}, []string{"app.example.com"}, nil},
103+
{"catch-all path", []networking.IngressRule{{Host: "app.example.com", IngressRuleValue: networking.IngressRuleValue{HTTP: &networking.HTTPIngressRuleValue{Paths: []networking.HTTPIngressPath{{Path: "/"}}}}}}, []string{"app.example.com"}, []string{"/"}},
104+
{"specific paths", []networking.IngressRule{{Host: "app.example.com", IngressRuleValue: networking.IngressRuleValue{HTTP: &networking.HTTPIngressRuleValue{Paths: []networking.HTTPIngressPath{{Path: "/login"}, {Path: "/admin"}}}}}}, []string{"app.example.com"}, []string{"/login", "/admin"}},
105+
} {
106+
t.Run(tt.name, func(t *testing.T) {
107+
assert.Equal(t, tt.hosts, extractor.getHosts(tt.rules))
108+
if len(tt.rules) > 0 {
109+
assert.Equal(t, tt.paths, extractor.getPaths(tt.rules[0]))
110+
}
111+
})
112+
}
113+
}
114+
115+
func TestKubernetesHostMatching(t *testing.T) {
116+
for _, tt := range []struct {
117+
name, host, hostname string
118+
want bool
119+
}{
120+
{"exact", "app.example.com", "app.example.com", true},
121+
{"case insensitive and trailing dot", "App.Example.com.", "app.example.com", true},
122+
{"wildcard", "*.example.com", "app.example.com", true},
123+
{"wildcard case insensitive", "*.Example.com", "App.example.com", true},
124+
{"wildcard excludes apex", "*.example.com", "example.com", false},
125+
{"wildcard excludes empty label", "*.example.com", ".example.com", false},
126+
{"wildcard excludes nested labels", "*.example.com", "deep.app.example.com", false},
127+
{"wildcard excludes other suffix", "*.example.com", "app.other.com", false},
128+
{"different host", "app.example.com", "other.example.com", false},
129+
{"empty host", "", "other.example.com", true},
130+
} {
131+
t.Run(tt.name, func(t *testing.T) {
132+
assert.Equal(t, tt.want, hostMatchesHostname(tt.host, tt.hostname))
133+
})
134+
}
135+
}
136+
137+
func TestKubernetesHostCoversName(t *testing.T) {
138+
for _, tt := range []struct {
139+
host, name string
140+
want bool
141+
}{
142+
{"", "dashboard", true},
143+
{"dashboard.example.com", "dashboard", true},
144+
{"Dashboard.example.com", "dashboard", true},
145+
{"*.example.com", "dashboard", true},
146+
{"other.example.com", "dashboard", false},
147+
} {
148+
t.Run(tt.host+"/"+tt.name, func(t *testing.T) {
149+
assert.Equal(t, tt.want, hostCoversName(tt.host, tt.name))
150+
})
151+
}
152+
}

0 commit comments

Comments
 (0)