fix(host): call RunnerService under ermite's /-/api path
The client called the service at the root of the base URL, where ermite serves its web pages (a POST there gets the web gate's 403). The fake servers mounted the handler at the root too, so the tests agreed with the client. They now mount it under /-/api like ermite.
- commit
- 2785ef1d8465ae668240c4f662e6c54b55bbf877
- author
- brice · · 2026-10-12 22:22 UTC
- parent
- 16c0ee5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +15 -5
M internal/host/fake_test.go +1 -2
| @@ -273,8 +273,7 @@ func (s *fakeServer) WatchJob(ctx context.Context, r *connect.Request[ermite.Wat | |||
| 273 | 273 | // start serves the handler over HTTP/1.1 on a local port (Connect streams server messages over it) and returns its URL. | |
| 274 | 274 | func (s *fakeServer) start(t *testing.T) string { | |
| 275 | 275 | t.Helper() | |
| 276 | - | _, h := ermiteconnect.NewRunnerServiceHandler(s) | |
| 277 | - | srv := httptest.NewServer(h) | |
| 276 | + | srv := httptest.NewServer(hosttest.Mount(ermiteconnect.NewRunnerServiceHandler(s))) | |
| 278 | 277 | t.Cleanup(srv.Close) | |
| 279 | 278 | return srv.URL | |
| 280 | 279 | } | |
| @@ -273,8 +273,7 @@ func (s *fakeServer) WatchJob(ctx context.Context, r *connect.Request[ermite.Wat | |||||
| 273 | // start serves the handler over HTTP/1.1 on a local port (Connect streams server messages over it) and returns its URL. | 273 | // start serves the handler over HTTP/1.1 on a local port (Connect streams server messages over it) and returns its URL. | ||
| 274 | func (s *fakeServer) start(t *testing.T) string { | 274 | func (s *fakeServer) start(t *testing.T) string { | ||
| 275 | t.Helper() | 275 | t.Helper() | ||
| 276 | - | _, h := ermiteconnect.NewRunnerServiceHandler(s) | 276 | + | srv := httptest.NewServer(hosttest.Mount(ermiteconnect.NewRunnerServiceHandler(s))) |
| 277 | - | srv := httptest.NewServer(h) | |||
| 278 | t.Cleanup(srv.Close) | 277 | t.Cleanup(srv.Close) | ||
| 279 | return srv.URL | 278 | return srv.URL | ||
| 280 | } | 279 | } | ||
M internal/host/hosttest/hosttest.go +9 -2
| @@ -60,11 +60,18 @@ type jobState struct { | |||
| 60 | 60 | complete *ermite.CompleteJobRequest | |
| 61 | 61 | } | |
| 62 | 62 | ||
| 63 | + | // Mount serves a RunnerService handler where ermite does: under /-/api, so | |
| 64 | + | // that a client that forgets the prefix fails here as it would against ermite. | |
| 65 | + | func Mount(path string, h http.Handler) http.Handler { | |
| 66 | + | mux := http.NewServeMux() | |
| 67 | + | mux.Handle("/-/api"+path, http.StripPrefix("/-/api", h)) | |
| 68 | + | return mux | |
| 69 | + | } | |
| 70 | + | ||
| 63 | 71 | // New starts the server; Close stops it. | |
| 64 | 72 | func New() *Server { | |
| 65 | 73 | s := &Server{jobs: map[string]*jobState{}, assigned: map[string]*ermite.Job{}, done: make(chan string, 256)} | |
| 66 | - | _, h := ermiteconnect.NewRunnerServiceHandler(s) | |
| 67 | - | s.srv = httptest.NewServer(h) | |
| 74 | + | s.srv = httptest.NewServer(Mount(ermiteconnect.NewRunnerServiceHandler(s))) | |
| 68 | 75 | s.URL = s.srv.URL | |
| 69 | 76 | return s | |
| 70 | 77 | } | |
| @@ -60,11 +60,18 @@ type jobState struct { | |||||
| 60 | complete *ermite.CompleteJobRequest | 60 | complete *ermite.CompleteJobRequest | ||
| 61 | } | 61 | } | ||
| 62 | 62 | ||||
| 63 | + | // Mount serves a RunnerService handler where ermite does: under /-/api, so | |||
| 64 | + | // that a client that forgets the prefix fails here as it would against ermite. | |||
| 65 | + | func Mount(path string, h http.Handler) http.Handler { | |||
| 66 | + | mux := http.NewServeMux() | |||
| 67 | + | mux.Handle("/-/api"+path, http.StripPrefix("/-/api", h)) | |||
| 68 | + | return mux | |||
| 69 | + | } | |||
| 70 | + | ||||
| 63 | // New starts the server; Close stops it. | 71 | // New starts the server; Close stops it. | ||
| 64 | func New() *Server { | 72 | func New() *Server { | ||
| 65 | s := &Server{jobs: map[string]*jobState{}, assigned: map[string]*ermite.Job{}, done: make(chan string, 256)} | 73 | s := &Server{jobs: map[string]*jobState{}, assigned: map[string]*ermite.Job{}, done: make(chan string, 256)} | ||
| 66 | - | _, h := ermiteconnect.NewRunnerServiceHandler(s) | 74 | + | s.srv = httptest.NewServer(Mount(ermiteconnect.NewRunnerServiceHandler(s))) |
| 67 | - | s.srv = httptest.NewServer(h) | |||
| 68 | s.URL = s.srv.URL | 75 | s.URL = s.srv.URL | ||
| 69 | return s | 76 | return s | ||
| 70 | } | 77 | } | ||
M internal/host/rpc.go +5 -1
| @@ -103,9 +103,13 @@ func newClient(baseURL, token string, base http.RoundTripper, insecure bool) erm | |||
| 103 | 103 | Transport: bearer{token: token, base: base, allowInsecure: insecure}, | |
| 104 | 104 | CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, | |
| 105 | 105 | } | |
| 106 | - | return ermiteconnect.NewRunnerServiceClient(hc, strings.TrimRight(baseURL, "/")) | |
| 106 | + | return ermiteconnect.NewRunnerServiceClient(hc, strings.TrimRight(baseURL, "/")+APIPath) | |
| 107 | 107 | } | |
| 108 | 108 | ||
| 109 | + | // APIPath is where ermite serves its API under the base URL, as in ermite's own | |
| 110 | + | // client (internal/adapters/client.APIPath there). | |
| 111 | + | const APIPath = "/-/api" | |
| 112 | + | ||
| 109 | 113 | // ClientFactory builds a client for a token; tests replace it. | |
| 110 | 114 | type ClientFactory func(token string) ermiteconnect.RunnerServiceClient | |
| 111 | 115 | ||
| @@ -103,9 +103,13 @@ func newClient(baseURL, token string, base http.RoundTripper, insecure bool) erm | |||||
| 103 | Transport: bearer{token: token, base: base, allowInsecure: insecure}, | 103 | Transport: bearer{token: token, base: base, allowInsecure: insecure}, | ||
| 104 | CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, | 104 | CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, | ||
| 105 | } | 105 | } | ||
| 106 | - | return ermiteconnect.NewRunnerServiceClient(hc, strings.TrimRight(baseURL, "/")) | 106 | + | return ermiteconnect.NewRunnerServiceClient(hc, strings.TrimRight(baseURL, "/")+APIPath) |
| 107 | } | 107 | } | ||
| 108 | 108 | ||||
| 109 | + | // APIPath is where ermite serves its API under the base URL, as in ermite's own | |||
| 110 | + | // client (internal/adapters/client.APIPath there). | |||
| 111 | + | const APIPath = "/-/api" | |||
| 112 | + | ||||
| 109 | // ClientFactory builds a client for a token; tests replace it. | 113 | // ClientFactory builds a client for a token; tests replace it. | ||
| 110 | type ClientFactory func(token string) ermiteconnect.RunnerServiceClient | 114 | type ClientFactory func(token string) ermiteconnect.RunnerServiceClient | ||
| 111 | 115 | ||||