160 lines
4.6 KiB
Go
160 lines
4.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/burrow/forgejo-nsc/internal/forgejo"
|
|
"github.com/burrow/forgejo-nsc/internal/nsc"
|
|
)
|
|
|
|
type mockForgejo struct {
|
|
mu sync.Mutex
|
|
tokens []string
|
|
scopes []forgejo.Scope
|
|
err error
|
|
counter int
|
|
}
|
|
|
|
func (m *mockForgejo) RegistrationToken(ctx context.Context, scope forgejo.Scope) (string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.scopes = append(m.scopes, scope)
|
|
if m.err != nil {
|
|
return "", m.err
|
|
}
|
|
if m.counter >= len(m.tokens) {
|
|
return "", context.Canceled
|
|
}
|
|
tok := m.tokens[m.counter]
|
|
m.counter++
|
|
return tok, nil
|
|
}
|
|
|
|
type mockDispatcher struct {
|
|
mu sync.Mutex
|
|
requests []nsc.LaunchRequest
|
|
responses []string
|
|
err error
|
|
}
|
|
|
|
func (m *mockDispatcher) LaunchRunner(ctx context.Context, req nsc.LaunchRequest) (string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.err != nil {
|
|
return "", m.err
|
|
}
|
|
m.requests = append(m.requests, req)
|
|
idx := len(m.requests) - 1
|
|
if idx < len(m.responses) {
|
|
return m.responses[idx], nil
|
|
}
|
|
return "runner", nil
|
|
}
|
|
|
|
func TestServiceDispatchUsesDefaults(t *testing.T) {
|
|
forgejoMock := &mockForgejo{tokens: []string{"token"}}
|
|
dispatcherMock := &mockDispatcher{responses: []string{"runner-default"}}
|
|
|
|
cfg := Config{
|
|
DefaultScope: forgejo.Scope{Level: forgejo.ScopeInstance},
|
|
DefaultLabels: []string{"nscloud"},
|
|
InstanceURL: "https://forgejo.example.com",
|
|
DefaultTTL: 15 * time.Minute,
|
|
}
|
|
|
|
service := NewService(cfg, forgejoMock, dispatcherMock, nil)
|
|
|
|
resp, err := service.Dispatch(context.Background(), DispatchRequest{})
|
|
if err != nil {
|
|
t.Fatalf("Dispatch returned error: %v", err)
|
|
}
|
|
if len(resp.Runners) != 1 || resp.Runners[0].Name != "runner-default" {
|
|
t.Fatalf("unexpected dispatch response: %+v", resp)
|
|
}
|
|
|
|
if len(forgejoMock.scopes) != 1 || forgejoMock.scopes[0].Level != forgejo.ScopeInstance {
|
|
t.Fatalf("expected default scope, got %+v", forgejoMock.scopes)
|
|
}
|
|
|
|
if len(dispatcherMock.requests) != 1 {
|
|
t.Fatalf("expected one dispatcher call, got %d", len(dispatcherMock.requests))
|
|
}
|
|
req := dispatcherMock.requests[0]
|
|
if req.InstanceURL != cfg.InstanceURL {
|
|
t.Fatalf("expected instance URL %s, got %s", cfg.InstanceURL, req.InstanceURL)
|
|
}
|
|
if got := req.Labels; len(got) != 1 || got[0] != "nscloud" {
|
|
t.Fatalf("expected default labels, got %v", got)
|
|
}
|
|
if req.Duration != cfg.DefaultTTL {
|
|
t.Fatalf("expected duration %v, got %v", cfg.DefaultTTL, req.Duration)
|
|
}
|
|
}
|
|
|
|
func TestServiceDispatchCustomScopeAndCount(t *testing.T) {
|
|
forgejoMock := &mockForgejo{tokens: []string{"token-1", "token-2"}}
|
|
dispatcherMock := &mockDispatcher{responses: []string{"runner-1", "runner-2"}}
|
|
|
|
cfg := Config{
|
|
DefaultScope: forgejo.Scope{Level: forgejo.ScopeInstance},
|
|
DefaultLabels: []string{"default"},
|
|
InstanceURL: "https://forgejo.example.com",
|
|
DefaultTTL: 10 * time.Minute,
|
|
}
|
|
|
|
service := NewService(cfg, forgejoMock, dispatcherMock, nil)
|
|
|
|
reqScope := &Scope{Level: string(forgejo.ScopeRepository), Owner: "acme", Name: "repo"}
|
|
res, err := service.Dispatch(context.Background(), DispatchRequest{
|
|
Count: 2,
|
|
Labels: []string{"custom"},
|
|
Scope: reqScope,
|
|
TTL: 5 * time.Minute,
|
|
Machine: "4x8",
|
|
Image: "runner:latest",
|
|
ExtraEnv: map[string]string{"FOO": "bar"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Dispatch returned error: %v", err)
|
|
}
|
|
if len(res.Runners) != 2 {
|
|
t.Fatalf("expected two runners, got %+v", res)
|
|
}
|
|
|
|
if len(forgejoMock.scopes) != 2 {
|
|
t.Fatalf("expected two scope calls, got %d", len(forgejoMock.scopes))
|
|
}
|
|
for _, scope := range forgejoMock.scopes {
|
|
if scope.Level != forgejo.ScopeRepository || scope.Owner != "acme" || scope.Name != "repo" {
|
|
t.Fatalf("unexpected scope: %+v", scope)
|
|
}
|
|
}
|
|
|
|
if len(dispatcherMock.requests) != 2 {
|
|
t.Fatalf("expected two dispatcher calls, got %d", len(dispatcherMock.requests))
|
|
}
|
|
for _, call := range dispatcherMock.requests {
|
|
if call.MachineType != "4x8" || call.Image != "runner:latest" {
|
|
t.Fatalf("unexpected machine/image in %+v", call)
|
|
}
|
|
if call.Duration != 5*time.Minute {
|
|
t.Fatalf("expected TTL to override default, got %v", call.Duration)
|
|
}
|
|
if call.Labels[0] != "custom" {
|
|
t.Fatalf("expected custom labels, got %v", call.Labels)
|
|
}
|
|
if call.ExtraEnv["FOO"] != "bar" {
|
|
t.Fatalf("expected env passthrough, got %v", call.ExtraEnv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServiceDispatchErrorsWithoutLabels(t *testing.T) {
|
|
service := NewService(Config{DefaultScope: forgejo.Scope{Level: forgejo.ScopeInstance}}, &mockForgejo{}, &mockDispatcher{}, nil)
|
|
if _, err := service.Dispatch(context.Background(), DispatchRequest{}); err == nil {
|
|
t.Fatalf("expected error when no labels are available")
|
|
}
|
|
}
|