dev-pod-api-build/internal/k8s/runners_test.go
2026-04-16 04:16:36 +00:00

194 lines
5.5 KiB
Go

package k8s
import (
"context"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/model"
)
func defaultRunnerOpts() CreateRunnerPodOpts {
return CreateRunnerPodOpts{
User: "alice",
RunnerID: "runner-abc12345",
Tools: "go,rust",
Task: "implement feature",
RepoURL: "alice/myrepo",
Branch: "main",
CPUReq: "2",
MemReq: "4Gi",
ForgejoRunnerToken: "test-runner-token",
}
}
func TestCreateRunnerPod(t *testing.T) {
client := newTestClient()
ctx := context.Background()
if err := client.EnsureNamespace(ctx, "alice"); err != nil {
t.Fatalf("ensure namespace: %v", err)
}
opts := defaultRunnerOpts()
podName, err := client.CreateRunnerPod(ctx, opts)
if err != nil {
t.Fatalf("create runner pod: %v", err)
}
expectedPodName := model.RunnerPodName("runner-abc12345")
if podName != expectedPodName {
t.Errorf("expected pod name %q, got %q", expectedPodName, podName)
}
ns := model.NamespaceName("alice")
pod, err := client.Clientset.CoreV1().Pods(ns).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
t.Fatalf("get runner pod: %v", err)
}
if pod.Labels["app"] != "dev-pod-runner" {
t.Errorf("expected app label dev-pod-runner, got %q", pod.Labels["app"])
}
if pod.Labels["runner-id"] != "runner-abc12345" {
t.Errorf("expected runner-id label runner-abc12345, got %q", pod.Labels["runner-id"])
}
if pod.Labels["user"] != "alice" {
t.Errorf("expected user label alice, got %q", pod.Labels["user"])
}
if len(pod.Spec.Containers) != 2 {
t.Fatalf("expected 2 containers (runner + ipip-sidecar), got %d", len(pod.Spec.Containers))
}
container := pod.Spec.Containers[0]
if container.Name != "runner" {
t.Errorf("expected container name runner, got %q", container.Name)
}
sidecar := pod.Spec.Containers[1]
if sidecar.Name != "ipip-sidecar" {
t.Errorf("expected sidecar name ipip-sidecar, got %q", sidecar.Name)
}
if sidecar.SecurityContext == nil || !*sidecar.SecurityContext.Privileged {
t.Error("expected ipip-sidecar to be privileged")
}
envMap := make(map[string]string)
for _, e := range container.Env {
envMap[e.Name] = e.Value
}
if envMap["RUNNER_MODE"] != "true" {
t.Error("expected RUNNER_MODE=true")
}
if envMap["RUNNER_ID"] != "runner-abc12345" {
t.Errorf("expected RUNNER_ID=runner-abc12345, got %q", envMap["RUNNER_ID"])
}
if envMap["DEV_POD_API_URL"] == "" {
t.Error("expected DEV_POD_API_URL to be set")
}
if envMap["FORGEJO_RUNNER_TOKEN"] != "test-runner-token" {
t.Errorf("expected FORGEJO_RUNNER_TOKEN=test-runner-token, got %q", envMap["FORGEJO_RUNNER_TOKEN"])
}
if envMap["RUNNER_REPO"] != "alice/myrepo" {
t.Errorf("expected RUNNER_REPO=alice/myrepo, got %q", envMap["RUNNER_REPO"])
}
if envMap["RUNNER_BRANCH"] != "main" {
t.Errorf("expected RUNNER_BRANCH=main, got %q", envMap["RUNNER_BRANCH"])
}
if envMap["DEV_TOOLS"] != "go,rust" {
t.Errorf("expected DEV_TOOLS=go,rust, got %q", envMap["DEV_TOOLS"])
}
cpuReq := container.Resources.Requests.Cpu()
if cpuReq.String() != "2" {
t.Errorf("expected cpu request 2, got %s", cpuReq.String())
}
memReq := container.Resources.Requests.Memory()
if memReq.String() != "4Gi" {
t.Errorf("expected memory request 4Gi, got %s", memReq.String())
}
}
func TestCreateRunnerPod_DefaultResources(t *testing.T) {
client := newTestClient()
ctx := context.Background()
if err := client.EnsureNamespace(ctx, "alice"); err != nil {
t.Fatalf("ensure namespace: %v", err)
}
opts := CreateRunnerPodOpts{
User: "alice",
RunnerID: "runner-defaults",
RepoURL: "alice/repo",
}
podName, err := client.CreateRunnerPod(ctx, opts)
if err != nil {
t.Fatalf("create runner pod: %v", err)
}
ns := model.NamespaceName("alice")
pod, err := client.Clientset.CoreV1().Pods(ns).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
t.Fatalf("get runner pod: %v", err)
}
container := pod.Spec.Containers[0]
cpuReq := container.Resources.Requests.Cpu()
if cpuReq.String() != "2" {
t.Errorf("expected default cpu request 2, got %s", cpuReq.String())
}
memReq := container.Resources.Requests.Memory()
if memReq.String() != "4Gi" {
t.Errorf("expected default memory request 4Gi, got %s", memReq.String())
}
}
func TestDeleteRunnerPod(t *testing.T) {
client := newTestClient()
ctx := context.Background()
if err := client.EnsureNamespace(ctx, "alice"); err != nil {
t.Fatalf("ensure namespace: %v", err)
}
opts := defaultRunnerOpts()
podName, err := client.CreateRunnerPod(ctx, opts)
if err != nil {
t.Fatalf("create runner pod: %v", err)
}
if err := client.DeleteRunnerPod(ctx, "alice", podName); err != nil {
t.Fatalf("delete runner pod: %v", err)
}
ns := model.NamespaceName("alice")
pods, err := client.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
LabelSelector: "app=dev-pod-runner",
})
if err != nil {
t.Fatalf("list pods: %v", err)
}
if len(pods.Items) != 0 {
t.Errorf("expected 0 runner pods after delete, got %d", len(pods.Items))
}
}
func TestDeleteRunnerPod_NonExistent(t *testing.T) {
client := newTestClient()
ctx := context.Background()
err := client.DeleteRunnerPod(ctx, "alice", "nonexistent-pod")
if err != nil {
t.Fatalf("delete nonexistent pod should not fail, got: %v", err)
}
}
func TestRunnerPodName(t *testing.T) {
name := model.RunnerPodName("runner-abc123")
if name != "dev-pod-runner-abc123" {
t.Errorf("expected dev-pod-runner-abc123, got %s", name)
}
}