Files
Felis/internal/platform/bundle_test.go
T
flyemoji 47fcd90f75 feat(platform): add node orchestration and the felis entrypoint
The platform package that places servers across nodes and wires the operator, build, restore, and reaper subsystems, plus cmd/felis, the single binary that runs them.
2026-06-26 23:32:38 +09:00

142 lines
4.2 KiB
Go

package platform
import (
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
// TestObjects_EveryDocHasTypeMeta enforces that every rendered object carries an
// apiVersion and a kind. The build/restore packages build their SAs/NetworkPolicy
// without TypeMeta, so this guards the stamping in bundle.go specifically.
func TestObjects_EveryDocHasTypeMeta(t *testing.T) {
for _, obj := range Objects(testParams()) {
gvk := obj.GetObjectKind().GroupVersionKind()
if gvk.Kind == "" || gvk.Version == "" {
t.Errorf("%T %s/%s has empty TypeMeta (kind=%q version=%q)",
obj, obj.GetNamespace(), obj.GetName(), gvk.Kind, gvk.Version)
}
}
}
// TestObjects_NamespacesLabeled checks the three namespaces are rendered with the
// immutable name label the NetworkPolicy namespaceSelectors key on.
func TestObjects_NamespacesLabeled(t *testing.T) {
want := map[string]bool{"felis": false, "minecraft": false, "felis-build": false}
for _, obj := range Objects(testParams()) {
ns, ok := obj.(*corev1.Namespace)
if !ok {
continue
}
if _, expected := want[ns.Name]; expected {
want[ns.Name] = true
}
if got := ns.Labels["kubernetes.io/metadata.name"]; got != ns.Name {
t.Errorf("namespace %q metadata.name label = %q, want %q", ns.Name, got, ns.Name)
}
}
for name, found := range want {
if !found {
t.Errorf("namespace %q not rendered", name)
}
}
}
// TestWeakJobSAs_Isolated proves the build/restore SAs are present, disable token
// auto-mounting, and — the key isolation invariant — are referenced by NO
// RoleBinding anywhere. Their powerlessness is the absence of any binding.
func TestWeakJobSAs_Isolated(t *testing.T) {
objs := Objects(testParams())
var build, restore *corev1.ServiceAccount
for _, obj := range objs {
sa, ok := obj.(*corev1.ServiceAccount)
if !ok {
continue
}
switch sa.Name {
case SABuild:
build = sa
case SARestore:
restore = sa
}
}
if build == nil {
t.Fatal("build SA not rendered")
}
if restore == nil {
t.Fatal("restore SA not rendered")
}
for _, sa := range []*corev1.ServiceAccount{build, restore} {
if sa.AutomountServiceAccountToken == nil || *sa.AutomountServiceAccountToken {
t.Errorf("%s must set AutomountServiceAccountToken=false", sa.Name)
}
}
// No RoleBinding may name the weak SAs as a subject.
for _, rb := range ControlPlaneRBAC(testParams()).RoleBindings {
for _, s := range rb.Subjects {
if s.Name == SABuild || s.Name == SARestore {
t.Errorf("binding %q must NOT grant any Role to weak SA %q", rb.Name, s.Name)
}
}
}
}
// TestRenderYAML_ParsesAndIsFenced renders the full bundle and asserts: every
// document parses with an apiVersion+kind, the expected kinds are present, and the
// stream contains no cluster-scoped RBAC (the ClusterRole/ClusterRoleBinding red
// line, checked on the literal output the way CI would).
func TestRenderYAML_ParsesAndIsFenced(t *testing.T) {
out, err := RenderYAML(testParams())
if err != nil {
t.Fatalf("RenderYAML: %v", err)
}
text := string(out)
if strings.Contains(text, "ClusterRole") {
t.Error("rendered bundle must not contain ClusterRole or ClusterRoleBinding")
}
kinds := map[string]bool{}
for _, doc := range strings.Split(text, "\n---\n") {
doc = strings.TrimSpace(doc)
if doc == "" {
continue
}
var m map[string]interface{}
if err := yaml.Unmarshal([]byte(doc), &m); err != nil {
t.Fatalf("doc does not parse: %v\n---\n%s", err, doc)
}
kind, _ := m["kind"].(string)
apiVersion, _ := m["apiVersion"].(string)
if kind == "" || apiVersion == "" {
t.Errorf("doc missing apiVersion/kind: %s", doc)
}
kinds[kind] = true
}
for _, want := range []string{"Namespace", "ServiceAccount", "Role", "RoleBinding", "NetworkPolicy"} {
if !kinds[want] {
t.Errorf("rendered bundle is missing a %s", want)
}
}
}
// TestRenderYAML_Deterministic guards that the render is stable (no map-ordering
// nondeterminism leaking into the manifest), so a regenerated bundle diffs cleanly.
func TestRenderYAML_Deterministic(t *testing.T) {
a, err := RenderYAML(testParams())
if err != nil {
t.Fatal(err)
}
b, err := RenderYAML(testParams())
if err != nil {
t.Fatal(err)
}
if string(a) != string(b) {
t.Error("RenderYAML must be deterministic across calls")
}
}