diff options
Diffstat (limited to 'worker/swift')
| -rw-r--r-- | worker/swift/exec_test.go | 149 | ||||
| -rw-r--r-- | worker/swift/handlers_test.go | 66 | ||||
| -rw-r--r-- | worker/swift/justfile | 5 | ||||
| -rw-r--r-- | worker/swift/models_test.go | 70 |
4 files changed, 289 insertions, 1 deletions
diff --git a/worker/swift/exec_test.go b/worker/swift/exec_test.go new file mode 100644 index 0000000..ead2dc6 --- /dev/null +++ b/worker/swift/exec_test.go @@ -0,0 +1,149 @@ +package main + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "testing" + "time" +) + +func TestConvertCommandErrorToResultType(t *testing.T) { + tests := []struct { + name string + err error + defaultStatus string + want string + }{ + {"nil error returns success", nil, resultRuntimeError, resultSuccess}, + {"DeadlineExceeded returns timeout", context.DeadlineExceeded, resultRuntimeError, resultTimeout}, + {"other error returns default status", os.ErrNotExist, resultCompileError, resultCompileError}, + {"other error returns runtime_error default", os.ErrPermission, resultRuntimeError, resultRuntimeError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := convertCommandErrorToResultType(tt.err, tt.defaultStatus) + if got != tt.want { + t.Errorf("convertCommandErrorToResultType() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestExecCommandWithTimeout_Success(t *testing.T) { + stdout, stderr, err := execCommandWithTimeout( + context.Background(), + t.TempDir(), + 5*time.Second, + func(ctx context.Context) *exec.Cmd { + return exec.CommandContext(ctx, "echo", "hello") + }, + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if stdout != "hello\n" { + t.Errorf("stdout = %q, want %q", stdout, "hello\n") + } + if stderr != "" { + t.Errorf("stderr = %q, want empty", stderr) + } +} + +func TestExecCommandWithTimeout_Failure(t *testing.T) { + _, _, err := execCommandWithTimeout( + context.Background(), + t.TempDir(), + 5*time.Second, + func(ctx context.Context) *exec.Cmd { + return exec.CommandContext(ctx, "false") + }, + ) + if err == nil { + t.Fatal("expected error, got nil") + } +} + +func TestExecCommandWithTimeout_Timeout(t *testing.T) { + _, _, err := execCommandWithTimeout( + context.Background(), + t.TempDir(), + 50*time.Millisecond, + func(ctx context.Context) *exec.Cmd { + return exec.CommandContext(ctx, "sleep", "10") + }, + ) + if err != context.DeadlineExceeded { + t.Errorf("expected DeadlineExceeded, got %v", err) + } +} + +func TestExecCommandWithTimeout_Stderr(t *testing.T) { + _, stderr, _ := execCommandWithTimeout( + context.Background(), + t.TempDir(), + 5*time.Second, + func(ctx context.Context) *exec.Cmd { + return exec.CommandContext(ctx, "sh", "-c", "echo errmsg >&2") + }, + ) + if stderr != "errmsg\n" { + t.Errorf("stderr = %q, want %q", stderr, "errmsg\n") + } +} + +func TestPrepareWorkingDir(t *testing.T) { + dir := filepath.Join(t.TempDir(), "subdir") + res := prepareWorkingDir(dir) + if res.Status != resultSuccess { + t.Fatalf("prepareWorkingDir() status = %q, want %q", res.Status, resultSuccess) + } + info, err := os.Stat(dir) + if err != nil { + t.Fatalf("directory not created: %v", err) + } + if !info.IsDir() { + t.Fatalf("expected directory, got file") + } +} + +func TestPutSwiftSourceFile(t *testing.T) { + t.Run("writes file when Sources/ exists", func(t *testing.T) { + dir := t.TempDir() + sourcesDir := filepath.Join(dir, "Sources") + if err := os.MkdirAll(sourcesDir, 0755); err != nil { + t.Fatalf("failed to create Sources dir: %v", err) + } + res := putSwiftSourceFile(dir, "print(\"hello\")") + if res.Status != resultSuccess { + t.Fatalf("putSwiftSourceFile() status = %q, want %q", res.Status, resultSuccess) + } + content, err := os.ReadFile(filepath.Join(sourcesDir, "main.swift")) + if err != nil { + t.Fatalf("failed to read file: %v", err) + } + if string(content) != "print(\"hello\")" { + t.Errorf("file content = %q, want %q", string(content), "print(\"hello\")") + } + }) + + t.Run("returns error when Sources/ does not exist", func(t *testing.T) { + dir := t.TempDir() + res := putSwiftSourceFile(dir, "print(\"hello\")") + if res.Status == resultSuccess { + t.Fatal("expected error status, got success") + } + }) +} + +func TestRemoveWorkingDir(t *testing.T) { + dir := filepath.Join(t.TempDir(), "toremove") + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatalf("failed to create dir: %v", err) + } + removeWorkingDir(dir) + if _, err := os.Stat(dir); !os.IsNotExist(err) { + t.Errorf("directory still exists after removeWorkingDir") + } +} diff --git a/worker/swift/handlers_test.go b/worker/swift/handlers_test.go new file mode 100644 index 0000000..f9c2e0b --- /dev/null +++ b/worker/swift/handlers_test.go @@ -0,0 +1,66 @@ +package main + +import ( + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/labstack/echo/v4" +) + +func TestNewBadRequestError(t *testing.T) { + httpErr := newBadRequestError(errors.New("test error")) + if httpErr.Code != http.StatusBadRequest { + t.Errorf("status code = %d, want %d", httpErr.Code, http.StatusBadRequest) + } + msg, ok := httpErr.Message.(string) + if !ok { + t.Fatalf("expected string message, got %T", httpErr.Message) + } + if !strings.Contains(msg, "test error") { + t.Errorf("message = %q, want it to contain %q", msg, "test error") + } +} + +func TestHandleExec_InvalidJSON(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/exec", strings.NewReader("not json")) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := handleExec(c) + if err == nil { + t.Fatal("expected error, got nil") + } + httpErr, ok := err.(*echo.HTTPError) + if !ok { + t.Fatalf("expected *echo.HTTPError, got %T", err) + } + if httpErr.Code != http.StatusBadRequest { + t.Errorf("status code = %d, want %d", httpErr.Code, http.StatusBadRequest) + } +} + +func TestHandleExec_ZeroMaxDuration(t *testing.T) { + e := echo.New() + body := `{"code":"print(1)","code_hash":"abc","stdin":"","max_duration_ms":0}` + req := httptest.NewRequest(http.MethodPost, "/exec", strings.NewReader(body)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := handleExec(c) + if err == nil { + t.Fatal("expected error, got nil") + } + httpErr, ok := err.(*echo.HTTPError) + if !ok { + t.Fatalf("expected *echo.HTTPError, got %T", err) + } + if httpErr.Code != http.StatusBadRequest { + t.Errorf("status code = %d, want %d", httpErr.Code, http.StatusBadRequest) + } +} diff --git a/worker/swift/justfile b/worker/swift/justfile index b203597..decaaf0 100644 --- a/worker/swift/justfile +++ b/worker/swift/justfile @@ -2,4 +2,7 @@ check: go build -o /dev/null ./... go tool golangci-lint run -ci: check +test: + go test ./... + +ci: check test diff --git a/worker/swift/models_test.go b/worker/swift/models_test.go new file mode 100644 index 0000000..62d6675 --- /dev/null +++ b/worker/swift/models_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "testing" + "time" +) + +func TestExecRequestData_Validate(t *testing.T) { + tests := []struct { + name string + maxDurationMs int + wantErr error + }{ + {"positive value", 1000, nil}, + {"zero", 0, errInvalidMaxDuration}, + {"negative value", -1, errInvalidMaxDuration}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &execRequestData{MaxDurationMilliseconds: tt.maxDurationMs} + err := req.validate() + if err != tt.wantErr { + t.Errorf("validate() = %v, want %v", err, tt.wantErr) + } + }) + } +} + +func TestExecRequestData_MaxDuration(t *testing.T) { + tests := []struct { + name string + maxDurationMs int + want time.Duration + }{ + {"1000ms", 1000, 1 * time.Second}, + {"500ms", 500, 500 * time.Millisecond}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &execRequestData{MaxDurationMilliseconds: tt.maxDurationMs} + got := req.maxDuration() + if got != tt.want { + t.Errorf("maxDuration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestExecResponseData_Success(t *testing.T) { + tests := []struct { + name string + status string + want bool + }{ + {"success", resultSuccess, true}, + {"compile_error", resultCompileError, false}, + {"runtime_error", resultRuntimeError, false}, + {"timeout", resultTimeout, false}, + {"internal_error", resultInternalError, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res := &execResponseData{Status: tt.status} + got := res.success() + if got != tt.want { + t.Errorf("success() = %v, want %v", got, tt.want) + } + }) + } +} |
