Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions internal/ui/handlers/cancel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package handlers

import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/bitdynamics-ab/canton-devkit/internal/localnet"
"github.com/bitdynamics-ab/canton-devkit/internal/ui/progress"
"github.com/bitdynamics-ab/canton-devkit/internal/ui/stream"
)
Expand Down Expand Up @@ -91,6 +93,14 @@ func TestCancelUp_HappyPath_204AndCancelledEventEmitted(t *testing.T) {
mux := http.NewServeMux()
MountInstances(mux, hub)

// Must block so the job is still in-flight when the DELETE lands.
origUp := runUp
t.Cleanup(func() { runUp = origUp })
runUp = func(ctx context.Context, _ localnet.Progress, _ *localnet.UpOptions) int {
<-ctx.Done()
return localnet.ExitUserError
}

// Subscribe BEFORE the POST so we don't depend on the
// replay buffer for this test (cleaner ordering check).
hub.EnableBuffering(progress.TopicFor("cancelme"), 32)
Expand Down
9 changes: 9 additions & 0 deletions internal/ui/handlers/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/bitdynamics-ab/canton-devkit/internal/api/types"
"github.com/bitdynamics-ab/canton-devkit/internal/localnet"
"github.com/bitdynamics-ab/canton-devkit/internal/splice"
"github.com/bitdynamics-ab/canton-devkit/internal/ui/progress"
"github.com/bitdynamics-ab/canton-devkit/internal/ui/stream"
Expand Down Expand Up @@ -179,6 +180,14 @@ func TestCreate_DuplicateNameReturns409(t *testing.T) {
defer hub.Close()
handler := handleCreate(hub)

// Must block so the first job is still in-flight for the second POST.
origUp := runUp
t.Cleanup(func() { runUp = origUp })
runUp = func(ctx context.Context, _ localnet.Progress, _ *localnet.UpOptions) int {
<-ctx.Done()
return localnet.ExitUserError
}

req := httptest.NewRequest(http.MethodPost, "/api/instances",
strings.NewReader(`{"name":"dupcreate"}`))
rec1 := httptest.NewRecorder()
Expand Down
22 changes: 11 additions & 11 deletions internal/ui/handlers/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ func handleStopInstance() http.HandlerFunc {
}
}

// Indirected so tests can drive the bring-up paths without booting a
// real Canton stack: RunUp outlives the request on a detached context.
var (
listContainers = containers.List
runStart = localnet.RunStart
runUp = localnet.RunUp
)

// handleStartInstance: POST /api/instances/{name}/start.
//
// Mirrors the CLI's intelligent `localnet start`:
Expand All @@ -173,14 +181,6 @@ func handleStopInstance() http.HandlerFunc {
//
// The 204-vs-202 split lets the frontend branch: 204 → just refetch;
// 202 → open the existing create-progress modal.
// Indirected so tests can drive the start path without booting a real
// Canton stack, which outlives the request and skews every later run.
var (
listContainers = containers.List
runStart = localnet.RunStart
runUp = localnet.RunUp
)

func handleStartInstance(hub *stream.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
Expand Down Expand Up @@ -765,7 +765,7 @@ func handleCreate(hub *stream.Hub) http.HandlerFunc {
defer jobs.Unregister(req.Name)

prog := progress.New(hub, req.Name)
exitCode := localnet.RunUp(jobCtx, prog, opts)
exitCode := runUp(jobCtx, prog, opts)
log.Printf("create instance %q: exit_code=%d", req.Name, exitCode)
}()

Expand Down Expand Up @@ -873,7 +873,7 @@ func handleResumeInstance(hub *stream.Hub) http.HandlerFunc {
defer hub.ClearBuffer(topic)
defer jobs.Unregister(name)
prog := progress.New(hub, name)
exitCode := localnet.RunUp(jobCtx, prog, opts)
exitCode := runUp(jobCtx, prog, opts)
log.Printf("resume instance %q: exit_code=%d", name, exitCode)
}()

Expand Down Expand Up @@ -1076,7 +1076,7 @@ func realRecreateWork(ctx context.Context, hub *stream.Hub, name, version string
Version: version,
Profiles: profiles,
}
exitCode := localnet.RunUp(ctx, prog, upOpts)
exitCode := runUp(ctx, prog, upOpts)
log.Printf("restart instance %q: down_exit=%d up_exit=%d",
name, downExit, exitCode)
}
Expand Down
8 changes: 8 additions & 0 deletions internal/ui/handlers/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/bitdynamics-ab/canton-devkit/internal/api/types"
"github.com/bitdynamics-ab/canton-devkit/internal/localnet"
"github.com/bitdynamics-ab/canton-devkit/internal/splice"
)

Expand Down Expand Up @@ -43,6 +44,13 @@ func TestMain(m *testing.M) {
return types.PreflightReport{SchemaVersion: types.SchemaVersion, OK: true}
}

// Bring-up runs on a detached context and outlives the test, so a
// default no-op keeps any test from stranding a real Canton stack.
// Tests asserting on bring-up override this with a recording stub.
runUp = func(context.Context, localnet.Progress, *localnet.UpOptions) int {
return localnet.ExitSuccess
}

code := m.Run()
_ = os.RemoveAll(root)
os.Exit(code)
Expand Down
30 changes: 26 additions & 4 deletions internal/ui/handlers/resume_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package handlers

import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/bitdynamics-ab/canton-devkit/internal/localnet"
"github.com/bitdynamics-ab/canton-devkit/internal/registry"
"github.com/bitdynamics-ab/canton-devkit/internal/ui/stream"
)
Expand All @@ -26,14 +29,21 @@ func resumeMux(t *testing.T) (*httptest.Server, *stream.Hub) {

// TestResume_StoppedInstance202 — the happy path the user-reported
// bug needs: instance is stopped (registry says so, containers are
// gone), and POST /up brings it back up. We can't easily assert the
// goroutine reaches RunUp (it talks to docker), but we CAN assert
// the handler accepts the request, returns 202, and points the
// caller at the events stream.
// gone), and POST /up brings it back up. The recording stub also pins
// the no-silent-upgrade rule: resume reuses the recorded version.
func TestResume_StoppedInstance202(t *testing.T) {
t.Setenv("CANTON_DEVKIT_REGISTRY", t.TempDir())
seedInstance(t, "pebble", "0.6.4",
map[string]int{"app_user_ui": 44440}, registry.StatusStopped)

upOpts := make(chan *localnet.UpOptions, 1)
origUp := runUp
t.Cleanup(func() { runUp = origUp })
runUp = func(_ context.Context, _ localnet.Progress, opts *localnet.UpOptions) int {
upOpts <- opts
return localnet.ExitSuccess
}

srv, _ := resumeMux(t)

resp, err := http.Post(srv.URL+"/api/instances/pebble/up",
Expand All @@ -48,6 +58,18 @@ func TestResume_StoppedInstance202(t *testing.T) {
if h := resp.Header.Get("Content-Type"); h != "application/json" {
t.Errorf("Content-Type = %q, want application/json", h)
}

select {
case opts := <-upOpts:
if opts.Name != "pebble" {
t.Errorf("bring-up name = %q, want pebble", opts.Name)
}
if opts.Version != "0.6.4" {
t.Errorf("bring-up version = %q, want 0.6.4", opts.Version)
}
case <-time.After(5 * time.Second):
t.Fatal("resume never reached bring-up")
}
}

// TestResume_UnknownInstance404 — a name that's not in the registry
Expand Down
Loading