package resources import ( "testing" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1alpha1 "github.com/YuzuZensai/Minikura/operator/api/v1alpha1" ) func testServer() *v1alpha1.MinecraftServer { return &v1alpha1.MinecraftServer{ ObjectMeta: metav1.ObjectMeta{Name: "smp", Namespace: "minikura"}, Spec: v1alpha1.MinecraftServerSpec{ Type: v1alpha1.ServerStateful, ListenPort: 25565, ServiceType: v1alpha1.ExposureClusterIP, JarType: "PAPER", MinecraftVersion: "1.20.4", StorageSize: "10Gi", Resources: v1alpha1.Resources{MemoryLimitMB: 4096}, }, } } func envValue(env []corev1.EnvVar, name string) (string, bool) { for i := len(env) - 1; i >= 0; i-- { if env[i].Name == name { return env[i].Value, true } } return "", false } func TestMinecraftEnvCarriesSpecConfig(t *testing.T) { mc := testServer() mc.Spec.Properties = v1alpha1.MinecraftProperties{ Difficulty: "HARD", GameMode: "SURVIVAL", MaxPlayers: 50, MOTD: "welcome", } env := minecraftEnv(mc) for _, want := range []struct{ key, value string }{ {"TYPE", "PAPER"}, {"VERSION", "1.20.4"}, {"DIFFICULTY", "HARD"}, {"MAX_PLAYERS", "50"}, {"MOTD", "welcome"}, } { got, ok := envValue(env, want.key) if !ok { t.Errorf("%s missing from env", want.key) continue } if got != want.value { t.Errorf("%s = %q, want %q", want.key, got, want.value) } } } func TestOptionalPropertiesOmitted(t *testing.T) { env := minecraftEnv(testServer()) for _, key := range []string{"MOTD", "SEED", "LEVEL_TYPE"} { if _, ok := envValue(env, key); ok { t.Errorf("%s should be omitted when unset", key) } } } func TestServiceNodePortPinned(t *testing.T) { mc := testServer() mc.Spec.ServiceType = v1alpha1.ExposureNodePort mc.Spec.NodePort = 30123 svc := MinecraftService(mc) if svc.Spec.Type != corev1.ServiceTypeNodePort { t.Fatalf("type = %v, want NodePort", svc.Spec.Type) } if svc.Spec.Ports[0].NodePort != 30123 { t.Errorf("nodePort = %d, want 30123", svc.Spec.Ports[0].NodePort) } } func TestServiceNodePortUnsetLeavesAllocation(t *testing.T) { mc := testServer() mc.Spec.ServiceType = v1alpha1.ExposureNodePort svc := MinecraftService(mc) if svc.Spec.Ports[0].NodePort != 0 { t.Errorf("nodePort = %d, want 0 so the API server allocates one", svc.Spec.Ports[0].NodePort) } } func TestSelectorExcludesMutableLabels(t *testing.T) { mc := testServer() sts, err := MinecraftStatefulSet(mc) if err != nil { t.Fatalf("unexpected error: %v", err) } selector := sts.Spec.Selector.MatchLabels if _, ok := selector[v1alpha1.LabelServerType]; ok { t.Error("selector must not include the mutable server-type label") } if selector["app"] != ServerName(mc.Name) { t.Errorf("selector app = %q, want %q", selector["app"], ServerName(mc.Name)) } for k, v := range selector { if sts.Spec.Template.Labels[k] != v { t.Errorf("pod template missing selector label %s=%s", k, v) } } } func TestStatefulSetStorage(t *testing.T) { sts, err := MinecraftStatefulSet(testServer()) if err != nil { t.Fatalf("unexpected error: %v", err) } claims := sts.Spec.VolumeClaimTemplates if len(claims) != 1 { t.Fatalf("volumeClaimTemplates = %d, want 1", len(claims)) } if got := claims[0].Spec.Resources.Requests.Storage().String(); got != "10Gi" { t.Errorf("storage = %s, want 10Gi", got) } } func TestStatefulSetRejectsBadStorageSize(t *testing.T) { mc := testServer() mc.Spec.StorageSize = "ten-gigs" if _, err := MinecraftStatefulSet(mc); err == nil { t.Error("expected an error for an unparseable storageSize") } } func TestStatelessHasNoDataVolume(t *testing.T) { mc := testServer() mc.Spec.Type = v1alpha1.ServerStateless dep := MinecraftDeployment(mc) for _, m := range dep.Spec.Template.Spec.Containers[0].VolumeMounts { if m.Name == "data" { t.Error("stateless server should not mount a data volume") } } } func TestMinecraftConfigMap(t *testing.T) { cm := MinecraftConfigMap(testServer()) if cm.Name != "minecraft-smp-config" { t.Errorf("name = %q", cm.Name) } if cm.Data["jar-type"] != "PAPER" || cm.Data["minecraft-version"] != "1.20.4" { t.Errorf("data = %v", cm.Data) } } func TestMinecraftAPIKeyAndOptionalEnv(t *testing.T) { mc := testServer() mc.Spec.APIKeySecretRef = "minikura-key" mc.Spec.Properties.LevelSeed = "abc" mc.Spec.Properties.LevelType = "flat" falseVal := false mc.Spec.Properties.OnlineMode = &falseVal env := minecraftEnv(mc) got, ok := envValue(env, "SEED") if !ok || got != "abc" { t.Errorf("SEED = %q, %v", got, ok) } got, ok = envValue(env, "LEVEL_TYPE") if !ok || got != "flat" { t.Errorf("LEVEL_TYPE = %q, %v", got, ok) } got, ok = envValue(env, "ONLINE_MODE") if !ok || got != "false" { t.Errorf("ONLINE_MODE = %q, %v", got, ok) } found := false for _, e := range env { if e.Name == "MINIKURA_API_KEY" { found = true if e.ValueFrom == nil || e.ValueFrom.SecretKeyRef == nil { t.Fatal("expected secret ref for API key") } if e.ValueFrom.SecretKeyRef.Name != "minikura-key" || e.ValueFrom.SecretKeyRef.Key != "api-key" { t.Errorf("secret ref = %+v", e.ValueFrom.SecretKeyRef) } } } if !found { t.Error("MINIKURA_API_KEY missing") } } func TestStatefulSetDefaultStorage(t *testing.T) { mc := testServer() mc.Spec.StorageSize = "" sts, err := MinecraftStatefulSet(mc) if err != nil { t.Fatalf("unexpected error: %v", err) } if got := sts.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests.Storage().String(); got != "1Gi" { t.Errorf("storage = %s, want 1Gi", got) } } func TestMinecraftServiceDefaultsToClusterIP(t *testing.T) { svc := MinecraftService(testServer()) if svc.Spec.Type != corev1.ServiceTypeClusterIP { t.Errorf("type = %v", svc.Spec.Type) } if svc.Spec.Ports[0].Port != 25565 { t.Errorf("port = %d", svc.Spec.Ports[0].Port) } }