test(models+handlers): add delivery mode + workspace status coverage #868
164
workspace-server/internal/handlers/workspace_dispatchers_test.go
Normal file
164
workspace-server/internal/handlers/workspace_dispatchers_test.go
Normal file
@ -0,0 +1,164 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/Molecule-AI/molecule-monorepo/platform/internal/models"
|
||||
)
|
||||
|
||||
// ==================== resolveDeliveryMode ====================
|
||||
// Covers workspace_dispatchers.go / registry.go:resolveDeliveryMode
|
||||
|
||||
func TestResolveDeliveryMode_PayloadModeWins(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
ctx := context.Background()
|
||||
for _, mode := range []string{models.DeliveryModePush, models.DeliveryModePoll} {
|
||||
got, err := h.resolveDeliveryMode(ctx, "ws-any-id", mode)
|
||||
if err != nil {
|
||||
t.Errorf("resolveDeliveryMode(payloadMode=%q) unexpected error: %v", mode, err)
|
||||
}
|
||||
if got != mode {
|
||||
t.Errorf("resolveDeliveryMode(payloadMode=%q) = %q, want %q", mode, got, mode)
|
||||
}
|
||||
}
|
||||
|
||||
// DB must NOT have been queried when payloadMode is set.
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("DB expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDeliveryMode_ExistingDeliveryMode(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
// Workspace row has existing delivery_mode = "poll"
|
||||
mock.ExpectQuery("SELECT delivery_mode, runtime FROM workspaces").
|
||||
WithArgs("ws-poll").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"delivery_mode", "runtime"}).
|
||||
AddRow("poll", "langgraph"))
|
||||
|
||||
ctx := context.Background()
|
||||
got, err := h.resolveDeliveryMode(ctx, "ws-poll", "")
|
||||
if err != nil {
|
||||
t.Errorf("resolveDeliveryMode() unexpected error: %v", err)
|
||||
}
|
||||
if got != models.DeliveryModePoll {
|
||||
t.Errorf("resolveDeliveryMode() = %q, want %q", got, models.DeliveryModePoll)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDeliveryMode_ExternalRuntime_DefaultsToPoll(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
// Row exists but delivery_mode is NULL; runtime = "external"
|
||||
mock.ExpectQuery("SELECT delivery_mode, runtime FROM workspaces").
|
||||
WithArgs("ws-external").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"delivery_mode", "runtime"}).
|
||||
AddRow(nil, "external"))
|
||||
|
||||
ctx := context.Background()
|
||||
got, err := h.resolveDeliveryMode(ctx, "ws-external", "")
|
||||
if err != nil {
|
||||
t.Errorf("resolveDeliveryMode() unexpected error: %v", err)
|
||||
}
|
||||
if got != models.DeliveryModePoll {
|
||||
t.Errorf("resolveDeliveryMode() = %q, want %q (external runtime)", got, models.DeliveryModePoll)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDeliveryMode_SelfHosted_DefaultsToPush(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
// Row exists; delivery_mode is NULL; runtime = "langgraph"
|
||||
mock.ExpectQuery("SELECT delivery_mode, runtime FROM workspaces").
|
||||
WithArgs("ws-self-hosted").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"delivery_mode", "runtime"}).
|
||||
AddRow(nil, "langgraph"))
|
||||
|
||||
ctx := context.Background()
|
||||
got, err := h.resolveDeliveryMode(ctx, "ws-self-hosted", "")
|
||||
if err != nil {
|
||||
t.Errorf("resolveDeliveryMode() unexpected error: %v", err)
|
||||
}
|
||||
if got != models.DeliveryModePush {
|
||||
t.Errorf("resolveDeliveryMode() = %q, want %q (self-hosted default)", got, models.DeliveryModePush)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDeliveryMode_NotFound_DefaultsToPush(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
// Row not found → sql.ErrNoRows → default push
|
||||
mock.ExpectQuery("SELECT delivery_mode, runtime FROM workspaces").
|
||||
WithArgs("ws-nonexistent").
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
ctx := context.Background()
|
||||
got, err := h.resolveDeliveryMode(ctx, "ws-nonexistent", "")
|
||||
if err != nil {
|
||||
t.Errorf("resolveDeliveryMode() unexpected error on no-rows: %v", err)
|
||||
}
|
||||
if got != models.DeliveryModePush {
|
||||
t.Errorf("resolveDeliveryMode() = %q, want %q (not-found default)", got, models.DeliveryModePush)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDeliveryMode_DBError_Propagated(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
mock.ExpectQuery("SELECT delivery_mode, runtime FROM workspaces").
|
||||
WithArgs("ws-error").
|
||||
WillReturnError(context.DeadlineExceeded)
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := h.resolveDeliveryMode(ctx, "ws-error", "")
|
||||
if err == nil {
|
||||
t.Errorf("resolveDeliveryMode() expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDeliveryMode_ExistingDeliveryModeEmptyString(t *testing.T) {
|
||||
// When the DB returns an empty (non-NULL) string for delivery_mode,
|
||||
// it falls through to the runtime check (not the existing.Valid path).
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewRegistryHandler(broadcaster)
|
||||
|
||||
// delivery_mode is explicitly empty string (not NULL), runtime = "langgraph"
|
||||
// → falls through to runtime check → "push" for non-external
|
||||
mock.ExpectQuery("SELECT delivery_mode, runtime FROM workspaces").
|
||||
WithArgs("ws-empty-mode").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"delivery_mode", "runtime"}).
|
||||
AddRow("", "langgraph"))
|
||||
|
||||
ctx := context.Background()
|
||||
got, err := h.resolveDeliveryMode(ctx, "ws-empty-mode", "")
|
||||
if err != nil {
|
||||
t.Errorf("resolveDeliveryMode() unexpected error: %v", err)
|
||||
}
|
||||
if got != models.DeliveryModePush {
|
||||
t.Errorf("resolveDeliveryMode() = %q, want %q", got, models.DeliveryModePush)
|
||||
}
|
||||
}
|
||||
100
workspace-server/internal/models/workspace_delivery_mode_test.go
Normal file
100
workspace-server/internal/models/workspace_delivery_mode_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
package models
|
||||
|
||||
import "testing"
|
||||
|
||||
// ==================== IsValidDeliveryMode ====================
|
||||
|
||||
func TestIsValidDeliveryMode_Valid(t *testing.T) {
|
||||
for _, mode := range []string{DeliveryModePush, DeliveryModePoll} {
|
||||
if !IsValidDeliveryMode(mode) {
|
||||
t.Errorf("IsValidDeliveryMode(%q) = false, want true", mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidDeliveryMode_Invalid(t *testing.T) {
|
||||
cases := []struct {
|
||||
val string
|
||||
want bool
|
||||
}{
|
||||
{"", false}, // empty string is not valid — callers must resolve the default
|
||||
{"pushx", false}, // typo
|
||||
{"pollx", false}, // typo
|
||||
{"PUSH", false}, // case-sensitive
|
||||
{"PUSH ", false}, // trailing space
|
||||
{"push ", false}, // trailing space
|
||||
{"hybrid", false}, // non-existent mode
|
||||
{"poll ", false}, // trailing space
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := IsValidDeliveryMode(tc.val)
|
||||
if got != tc.want {
|
||||
t.Errorf("IsValidDeliveryMode(%q) = %v, want %v", tc.val, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== WorkspaceStatus ====================
|
||||
|
||||
func TestWorkspaceStatus_String(t *testing.T) {
|
||||
statuses := []WorkspaceStatus{
|
||||
StatusProvisioning,
|
||||
StatusOnline,
|
||||
StatusOffline,
|
||||
StatusDegraded,
|
||||
StatusFailed,
|
||||
StatusRemoved,
|
||||
StatusPaused,
|
||||
StatusHibernated,
|
||||
StatusHibernating,
|
||||
StatusAwaitingAgent,
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if got := s.String(); got != string(s) {
|
||||
t.Errorf("WorkspaceStatus(%q).String() = %q, want %q", s, got, string(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllWorkspaceStatuses_Length(t *testing.T) {
|
||||
// The const block has 10 statuses; AllWorkspaceStatuses must match.
|
||||
if got := len(AllWorkspaceStatuses); got != 10 {
|
||||
t.Errorf("len(AllWorkspaceStatuses) = %d, want 10", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllWorkspaceStatuses_ContainsAllNamed(t *testing.T) {
|
||||
// Verify every named const appears in AllWorkspaceStatuses exactly once.
|
||||
named := []WorkspaceStatus{
|
||||
StatusProvisioning,
|
||||
StatusOnline,
|
||||
StatusOffline,
|
||||
StatusDegraded,
|
||||
StatusFailed,
|
||||
StatusRemoved,
|
||||
StatusPaused,
|
||||
StatusHibernated,
|
||||
StatusHibernating,
|
||||
StatusAwaitingAgent,
|
||||
}
|
||||
set := make(map[WorkspaceStatus]bool, len(AllWorkspaceStatuses))
|
||||
for _, s := range AllWorkspaceStatuses {
|
||||
set[s] = true
|
||||
}
|
||||
for _, s := range named {
|
||||
if !set[s] {
|
||||
t.Errorf("named status %q missing from AllWorkspaceStatuses", s)
|
||||
}
|
||||
}
|
||||
if len(set) != len(named) {
|
||||
t.Errorf("AllWorkspaceStatuses has %d unique entries, want %d", len(set), len(named))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllWorkspaceStatuses_NoEmpty(t *testing.T) {
|
||||
for _, s := range AllWorkspaceStatuses {
|
||||
if s == "" {
|
||||
t.Errorf("AllWorkspaceStatuses contains empty string")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user