mirror of
https://github.com/YuzuZensai/Minikura.git
synced 2026-09-14 03:09:50 +00:00
🧪 test: add operator reconcile coverage
This commit is contained in:
@@ -12,12 +12,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MinecraftImage = "itzg/minecraft-server"
|
||||
ProxyImage = "itzg/mc-proxy:latest"
|
||||
ContainerPort = 25565
|
||||
DefaultHeapPercent = 80
|
||||
MinecraftImage = "itzg/minecraft-server"
|
||||
ProxyImage = "itzg/mc-proxy:latest"
|
||||
ContainerPort = 25565
|
||||
DefaultHeapPercent = 80
|
||||
DefaultMemoryLimitMB = 2048
|
||||
MinHeapMB = 256
|
||||
)
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func ServiceType(exposure v1alpha1.ServiceExposure, fallback corev1.ServiceType) corev1.ServiceType {
|
||||
switch exposure {
|
||||
case v1alpha1.ExposureClusterIP:
|
||||
@@ -32,12 +36,15 @@ func ServiceType(exposure v1alpha1.ServiceExposure, fallback corev1.ServiceType)
|
||||
}
|
||||
|
||||
func HeapMB(limitMB int32, heapPercent int32) string {
|
||||
if limitMB <= 0 {
|
||||
limitMB = DefaultMemoryLimitMB
|
||||
}
|
||||
if heapPercent <= 0 || heapPercent > 100 {
|
||||
heapPercent = DefaultHeapPercent
|
||||
}
|
||||
heap := int64(limitMB) * int64(heapPercent) / 100
|
||||
if heap < 256 {
|
||||
heap = 256
|
||||
if heap < MinHeapMB {
|
||||
heap = MinHeapMB
|
||||
}
|
||||
return fmt.Sprintf("%dM", heap)
|
||||
}
|
||||
@@ -45,7 +52,7 @@ func HeapMB(limitMB int32, heapPercent int32) string {
|
||||
func ResourceRequirements(r v1alpha1.Resources) corev1.ResourceRequirements {
|
||||
limitMB := r.MemoryLimitMB
|
||||
if limitMB <= 0 {
|
||||
limitMB = 2048
|
||||
limitMB = DefaultMemoryLimitMB
|
||||
}
|
||||
requestMB := r.MemoryRequestMB
|
||||
if requestMB <= 0 || requestMB > limitMB {
|
||||
@@ -97,6 +104,7 @@ func UserEnv(base []corev1.EnvVar, extra []v1alpha1.EnvVar) []corev1.EnvVar {
|
||||
for _, e := range extra {
|
||||
if i, ok := index[e.Name]; ok {
|
||||
base[i].Value = e.Value
|
||||
base[i].ValueFrom = nil
|
||||
continue
|
||||
}
|
||||
index[e.Name] = len(base)
|
||||
|
||||
@@ -21,6 +21,7 @@ func TestHeapMB(t *testing.T) {
|
||||
{"out of range falls back", 1024, 150, "819M"},
|
||||
{"floor applies to tiny limits", 128, 80, "256M"},
|
||||
{"full allocation", 1000, 100, "1000M"},
|
||||
{"zero limit uses default memory", 0, 80, "1638M"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -49,6 +50,29 @@ func TestResourceRequirements(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("zero memory uses default", func(t *testing.T) {
|
||||
got := ResourceRequirements(v1alpha1.Resources{})
|
||||
want := resource.MustParse("2048Mi")
|
||||
if got.Limits.Memory().Cmp(want) != 0 {
|
||||
t.Errorf("limit memory = %v, want %v", got.Limits.Memory(), &want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit request below limit is kept", func(t *testing.T) {
|
||||
got := ResourceRequirements(v1alpha1.Resources{MemoryLimitMB: 4096, MemoryRequestMB: 1024})
|
||||
want := resource.MustParse("1024Mi")
|
||||
if got.Requests.Memory().Cmp(want) != 0 {
|
||||
t.Errorf("request memory = %v, want %v", got.Requests.Memory(), &want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid cpu request is kept", func(t *testing.T) {
|
||||
got := ResourceRequirements(v1alpha1.Resources{MemoryLimitMB: 1024, CPURequest: "250m"})
|
||||
if got.Requests.Cpu().String() != "250m" {
|
||||
t.Errorf("cpu request = %v, want 250m", got.Requests.Cpu())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid cpu strings are dropped", func(t *testing.T) {
|
||||
got := ResourceRequirements(v1alpha1.Resources{
|
||||
MemoryLimitMB: 1024,
|
||||
@@ -84,3 +108,88 @@ func TestUserEnvOverridesDefaults(t *testing.T) {
|
||||
t.Errorf("TYPE = %q, want PAPER", got[0].Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserEnvClearsValueFromOnOverride(t *testing.T) {
|
||||
base := []corev1.EnvVar{{
|
||||
Name: "MINIKURA_API_KEY",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{Key: "api-key"},
|
||||
},
|
||||
}}
|
||||
got := UserEnv(base, []v1alpha1.EnvVar{{Name: "MINIKURA_API_KEY", Value: "inline"}})
|
||||
if got[0].Value != "inline" {
|
||||
t.Errorf("value = %q, want inline", got[0].Value)
|
||||
}
|
||||
if got[0].ValueFrom != nil {
|
||||
t.Error("ValueFrom should be cleared when overridden")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserEnvAppendsUnknownKeys(t *testing.T) {
|
||||
base := []corev1.EnvVar{{Name: "TYPE", Value: "PAPER"}}
|
||||
got := UserEnv(base, []v1alpha1.EnvVar{{Name: "EXTRA", Value: "1"}})
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("len = %d, want 2", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJVMEnv(t *testing.T) {
|
||||
env := JVMEnv(v1alpha1.JVMOptions{
|
||||
Opts: "-XX:+UseG1GC",
|
||||
UseAikarFlags: true,
|
||||
UseMeowIceFlags: true,
|
||||
HeapPercent: 70,
|
||||
}, 2048)
|
||||
|
||||
want := map[string]string{
|
||||
"MEMORY": "1433M",
|
||||
"JVM_OPTS": "-XX:+UseG1GC",
|
||||
"USE_AIKAR_FLAGS": "true",
|
||||
"USE_MEOWICE_FLAGS": "true",
|
||||
}
|
||||
for key, value := range want {
|
||||
got, ok := envValue(env, key)
|
||||
if !ok || got != value {
|
||||
t.Errorf("%s = %q, %v; want %q", key, got, ok, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJVMEnvOmitsOptionalFlags(t *testing.T) {
|
||||
env := JVMEnv(v1alpha1.JVMOptions{}, 1024)
|
||||
if _, ok := envValue(env, "JVM_OPTS"); ok {
|
||||
t.Error("JVM_OPTS should be omitted")
|
||||
}
|
||||
if _, ok := envValue(env, "USE_AIKAR_FLAGS"); ok {
|
||||
t.Error("USE_AIKAR_FLAGS should be omitted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTCPProbeDefaultsPort(t *testing.T) {
|
||||
probe := TCPProbe(15, 0)
|
||||
if probe.TCPSocket.Port.IntVal != ContainerPort {
|
||||
t.Errorf("port = %d, want %d", probe.TCPSocket.Port.IntVal, ContainerPort)
|
||||
}
|
||||
if probe.InitialDelaySeconds != 15 {
|
||||
t.Errorf("initialDelay = %d", probe.InitialDelaySeconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolValue(t *testing.T) {
|
||||
if !BoolValue(nil, true) {
|
||||
t.Error("nil should use fallback true")
|
||||
}
|
||||
f := false
|
||||
if BoolValue(&f, true) {
|
||||
t.Error("explicit false should win")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceTypeLoadBalancer(t *testing.T) {
|
||||
if got := ServiceType(v1alpha1.ExposureLoadBalancer, corev1.ServiceTypeClusterIP); got != corev1.ServiceTypeLoadBalancer {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
if got := ServiceType(v1alpha1.ExposureClusterIP, corev1.ServiceTypeLoadBalancer); got != corev1.ServiceTypeClusterIP {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,5 +181,3 @@ func MinecraftStatefulSet(mc *v1alpha1.MinecraftServer) (*appsv1.StatefulSet, er
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
@@ -152,3 +152,74 @@ func TestStatelessHasNoDataVolume(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
v1alpha1 "github.com/YuzuZensai/Minikura/operator/api/v1alpha1"
|
||||
)
|
||||
|
||||
func TestServerAndProxyNames(t *testing.T) {
|
||||
if got := ServerName("smp"); got != "minecraft-smp" {
|
||||
t.Errorf("ServerName = %q", got)
|
||||
}
|
||||
if got := ProxyName(v1alpha1.ProxyVelocity, "edge"); got != "velocity-edge" {
|
||||
t.Errorf("ProxyName velocity = %q", got)
|
||||
}
|
||||
if got := ProxyName(v1alpha1.ProxyBungeeCord, "edge"); got != "bungeecord-edge" {
|
||||
t.Errorf("ProxyName bungee = %q", got)
|
||||
}
|
||||
if got := ConfigMapName("minecraft-smp"); got != "minecraft-smp-config" {
|
||||
t.Errorf("ConfigMapName = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerLabels(t *testing.T) {
|
||||
mc := &v1alpha1.MinecraftServer{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "smp"},
|
||||
Spec: v1alpha1.MinecraftServerSpec{Type: v1alpha1.ServerStateful},
|
||||
}
|
||||
labels := ServerLabels(mc)
|
||||
want := map[string]string{
|
||||
"app": "minecraft-smp",
|
||||
v1alpha1.LabelServerType: "stateful",
|
||||
v1alpha1.LabelServerID: "smp",
|
||||
v1alpha1.LabelManagedBy: v1alpha1.ManagerName,
|
||||
}
|
||||
for k, v := range want {
|
||||
if labels[k] != v {
|
||||
t.Errorf("label %s = %q, want %q", k, labels[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyLabels(t *testing.T) {
|
||||
rp := &v1alpha1.ReverseProxyServer{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "edge"},
|
||||
Spec: v1alpha1.ReverseProxyServerSpec{Type: v1alpha1.ProxyVelocity},
|
||||
}
|
||||
labels := ProxyLabels(rp)
|
||||
if labels["app"] != "velocity-edge" {
|
||||
t.Errorf("app = %q", labels["app"])
|
||||
}
|
||||
if labels[v1alpha1.LabelServerType] != "velocity" {
|
||||
t.Errorf("server-type = %q", labels[v1alpha1.LabelServerType])
|
||||
}
|
||||
if labels[v1alpha1.LabelProxyID] != "edge" {
|
||||
t.Errorf("proxy-id = %q", labels[v1alpha1.LabelProxyID])
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func ProxyDeployment(rp *v1alpha1.ReverseProxyServer) *appsv1.Deployment {
|
||||
{Name: "config", MountPath: "/config"},
|
||||
},
|
||||
ReadinessProbe: TCPProbe(30, rp.Spec.ListenPort),
|
||||
Resources: ResourceRequirements(rp.Spec.Resources),
|
||||
Resources: ResourceRequirements(rp.Spec.Resources),
|
||||
}},
|
||||
Volumes: []corev1.Volume{{
|
||||
Name: "config",
|
||||
|
||||
@@ -3,6 +3,7 @@ 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"
|
||||
@@ -41,3 +42,72 @@ func TestProxyEnvCarriesRuntimeConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyConfigMap(t *testing.T) {
|
||||
cm := ProxyConfigMap(testProxy())
|
||||
if cm.Name != "velocity-edge-config" {
|
||||
t.Errorf("name = %q", cm.Name)
|
||||
}
|
||||
if cm.Data["proxy-type"] != "VELOCITY" || cm.Data["external-address"] != "play.example.com" {
|
||||
t.Errorf("data = %v", cm.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyService(t *testing.T) {
|
||||
rp := testProxy()
|
||||
svc := ProxyService(rp)
|
||||
if svc.Spec.Type != corev1.ServiceTypeLoadBalancer {
|
||||
t.Errorf("type = %v", svc.Spec.Type)
|
||||
}
|
||||
if svc.Spec.Ports[0].Port != 25565 {
|
||||
t.Errorf("port = %d", svc.Spec.Ports[0].Port)
|
||||
}
|
||||
if svc.Spec.Ports[0].TargetPort.IntVal != 25577 {
|
||||
t.Errorf("targetPort = %d", svc.Spec.Ports[0].TargetPort.IntVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyServiceNodePort(t *testing.T) {
|
||||
rp := testProxy()
|
||||
rp.Spec.ServiceType = v1alpha1.ExposureNodePort
|
||||
rp.Spec.NodePort = 30555
|
||||
svc := ProxyService(rp)
|
||||
if svc.Spec.Type != corev1.ServiceTypeNodePort {
|
||||
t.Errorf("type = %v", svc.Spec.Type)
|
||||
}
|
||||
if svc.Spec.Ports[0].NodePort != 30555 {
|
||||
t.Errorf("nodePort = %d", svc.Spec.Ports[0].NodePort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyDeploymentListenPortAndProbe(t *testing.T) {
|
||||
dep := ProxyDeployment(testProxy())
|
||||
container := dep.Spec.Template.Spec.Containers[0]
|
||||
if container.Ports[0].ContainerPort != 25577 {
|
||||
t.Errorf("containerPort = %d", container.Ports[0].ContainerPort)
|
||||
}
|
||||
if container.ReadinessProbe == nil || container.ReadinessProbe.TCPSocket == nil {
|
||||
t.Fatal("expected TCP readiness probe")
|
||||
}
|
||||
if container.ReadinessProbe.TCPSocket.Port.IntVal != 25577 {
|
||||
t.Errorf("probe port = %d", container.ReadinessProbe.TCPSocket.Port.IntVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyAPIKeyEnv(t *testing.T) {
|
||||
rp := testProxy()
|
||||
rp.Spec.APIKeySecretRef = "proxy-key"
|
||||
env := proxyEnv(rp)
|
||||
found := false
|
||||
for _, e := range env {
|
||||
if e.Name == "MINIKURA_API_KEY" {
|
||||
found = true
|
||||
if e.ValueFrom == nil || e.ValueFrom.SecretKeyRef.Name != "proxy-key" {
|
||||
t.Errorf("secret ref = %+v", e.ValueFrom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("MINIKURA_API_KEY missing")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user