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
14 changes: 11 additions & 3 deletions internal/ui/handlers/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ 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 @@ -206,7 +214,7 @@ func handleStartInstance(hub *stream.Hub) http.HandlerFunc {

// stopped / failed / partial: fast-start iff the containers
// still exist; otherwise fall back to a full bring-up.
existing, listErr := containers.List(r.Context(), state.ComposeProject)
existing, listErr := listContainers(r.Context(), state.ComposeProject)
if listErr == nil && len(existing) > 0 {
ctx, cancel := context.WithTimeout(r.Context(), downTimeout)
defer cancel()
Expand All @@ -216,7 +224,7 @@ func handleStartInstance(hub *stream.Hub) http.HandlerFunc {
// The Progress sink is only used on RunStart's up-fallback
// path, which the containers-present branch can't reach, so
// a discard sink is safe here.
exit := localnet.RunStart(ctx,
exit := runStart(ctx,
localnet.NewTextProgress(io.Discard, io.Discard),
&outBuf, &errBuf,
&localnet.StartOptions{Name: name, SkipWait: true})
Expand Down Expand Up @@ -268,7 +276,7 @@ func startBringUp(w http.ResponseWriter, name, version string, profiles []string
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("start (bring-up) instance %q: exit_code=%d", name, exitCode)
}()

Expand Down
52 changes: 52 additions & 0 deletions internal/ui/handlers/stopstart_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package handlers

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

"github.com/bitdynamics-ab/canton-devkit/internal/localnet"
"github.com/bitdynamics-ab/canton-devkit/internal/localnet/containers"
"github.com/bitdynamics-ab/canton-devkit/internal/registry"
"github.com/bitdynamics-ab/canton-devkit/internal/ui/stream"
)
Expand All @@ -23,6 +29,29 @@ func stopStartMux(t *testing.T) *httptest.Server {
return srv
}

// Without stubbing, the bring-up branch really boots Canton, and the
// containers it leaves behind push every later run down the fast-start
// branch instead.
func stubStartPath(t *testing.T, existing []containers.Info) chan *localnet.UpOptions {
t.Helper()
upOpts := make(chan *localnet.UpOptions, 1)
origList, origUp, origStart := listContainers, runUp, runStart
t.Cleanup(func() { listContainers, runUp, runStart = origList, origUp, origStart })

listContainers = func(context.Context, string) ([]containers.Info, error) {
return existing, nil
}
runUp = func(_ context.Context, _ localnet.Progress, opts *localnet.UpOptions) int {
upOpts <- opts
return localnet.ExitSuccess
}
runStart = func(context.Context, localnet.Progress, io.Writer, io.Writer, *localnet.StartOptions) int {
t.Error("fast-start ran; with no containers the handler must bring up instead")
return localnet.ExitSuccess
}
return upOpts
}

func TestStop_InvalidName400(t *testing.T) {
t.Setenv("CANTON_DEVKIT_REGISTRY", t.TempDir())
srv := stopStartMux(t)
Expand Down Expand Up @@ -109,6 +138,7 @@ func TestStart_StoppedNoContainers202(t *testing.T) {
if err := registry.Write(state); err != nil {
t.Fatalf("write seeded profiles: %v", err)
}
upOpts := stubStartPath(t, nil)
srv := stopStartMux(t)
resp, err := http.Post(srv.URL+"/api/instances/pebble/start", "application/json", nil)
if err != nil {
Expand All @@ -121,6 +151,28 @@ func TestStart_StoppedNoContainers202(t *testing.T) {
if h := resp.Header.Get("Content-Type"); h != "application/json" {
t.Errorf("Content-Type = %q, want application/json", h)
}
var body struct {
Instance string `json:"instance"`
EventsURL string `json:"events_url"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
t.Fatalf("decode body: %v", err)
}
if body.EventsURL != "/api/instances/pebble/events" {
t.Errorf("events_url = %q", body.EventsURL)
}

select {
case opts := <-upOpts:
if opts.Name != "pebble" || opts.Version != "0.6.4" {
t.Errorf("bring-up opts = %q/%q, want pebble/0.6.4", opts.Name, opts.Version)
}
if len(opts.Profiles) != 5 {
t.Errorf("bring-up profiles = %v, want the 5 seeded", opts.Profiles)
}
case <-time.After(5 * time.Second):
t.Fatal("bring-up never ran")
}
}

// TestStartStub503WithoutHub — without a hub the /start route is
Expand Down
Loading