| new file mode 100644 |
| index 000000000..61b5ef7fe |
| --- /dev/null |
| +++ b/internal/console/apidomains_firstroute_test.go |
| @@ -0,0 +1,97 @@ |
| +package console |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "net/http" |
| + "strings" |
| + "testing" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/domains" |
| + "github.com/carlosframework/platform/internal/provision" |
| +) |
| + |
| +// firstRouteFixture gives titogo a first-route claim on woodstar.app — the |
| +// claim `carlos deploy --kind static --host` makes, with the static record |
| +// it declared at the same host. That record is titogo's only instance |
| +// record, which is exactly what used to make a route-less re-attach default |
| +// to the host itself. |
| +func firstRouteFixture(t *testing.T, code string) (*consoleFixture, string) { |
| + t.Helper() |
| + fx, member := instanceFixture(t, code) |
| + ctx := context.Background() |
| + if _, _, err := domains.AttachClaim(ctx, fx.rel, domains.Claim{ |
| + Host: "woodstar.app", Account: "bab", App: "titogo", CreatedAt: time.Now().UTC().Format(time.RFC3339), |
| + }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := provision.CreateRecord(ctx, fx.rel, provision.Record{ |
| + Schema: 1, Host: "woodstar.app", App: "titogo", Account: "bab", Channel: "edge", Kind: "static", |
| + CreatedAt: time.Now().UTC(), |
| + }); err != nil { |
| + t.Fatal(err) |
| + } |
| + return fx, member |
| +} |
| + |
| +func TestAttachKeepsAFirstRouteClaimFirstRoute(t *testing.T) { |
| + const zone = "acme.oncarlos.com" |
| + for name, body := range map[string]string{ |
| + "no route": `{"host":"woodstar.app"}`, |
| + "route is the host": `{"host":"woodstar.app","route":"WoodStar.app"}`, |
| + "wildcard flip": `{"host":"woodstar.app","wildcard":true}`, |
| + "wildcard and catchall": `{"host":"woodstar.app","wildcard":true,"catchall":true}`, |
| + } { |
| + t.Run(name, func(t *testing.T) { |
| + fx, member := firstRouteFixture(t, "code-dom-fr-"+strings.ReplaceAll(name, " ", "-")) |
| + fx.srv.delegationZone = zone |
| + fx.srv.lookupCNAME = func(_ context.Context, n string) (string, error) { |
| + if n == "_acme-challenge.woodstar.app" { |
| + return "_acme-challenge.woodstar.app." + zone + ".", nil |
| + } |
| + return "", errors.New("not found") |
| + } |
| + code, _, out := rawReq(t, http.MethodPost, domainsURL(fx, "bab", "titogo"), member, body) |
| + if code != http.StatusOK || !strings.Contains(out, `"route":"woodstar.app"`) || !strings.Contains(out, `"created":false`) { |
| + t.Fatalf("re-attach = %d %s", code, out) |
| + } |
| + c := claimOf(t, fx, "woodstar.app") |
| + if c.Route != "" { |
| + t.Fatalf("claim route = %q, want the first-route claim's empty route", c.Route) |
| + } |
| + if strings.Contains(body, "wildcard") && !c.Wildcard { |
| + t.Fatalf("claim = %+v, want Wildcard set", *c) |
| + } |
| + if strings.Contains(body, "catchall") && !c.Catchall { |
| + t.Fatalf("claim = %+v, want Catchall set", *c) |
| + } |
| + // Detach still takes the first-route branch: the record goes too. |
| + code, _, out = rawReq(t, http.MethodDelete, domainsURL(fx, "bab", "titogo")+"/woodstar.app", member, "") |
| + if code != http.StatusOK || !strings.Contains(out, `"removed":true`) { |
| + t.Fatalf("detach = %d %s", code, out) |
| + } |
| + if _, err := provision.GetRecord(context.Background(), fx.rel, "bab", "titogo", "woodstar.app"); err == nil { |
| + t.Fatal("the static record outlived its first-route claim") |
| + } |
| + }) |
| + } |
| +} |
| + |
| +// Another holder's claim does not take the branch: the ordinary path answers |
| +// with its neutral refusal, exactly as before. |
| +func TestAttachFirstRouteBranchIsOwnClaimsOnly(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-dom-fr-other") |
| + if _, _, err := domains.AttachClaim(context.Background(), fx.rel, domains.Claim{ |
| + Host: "woodstar.app", Account: "cdef", App: "widgets", CreatedAt: time.Now().UTC().Format(time.RFC3339), |
| + }); err != nil { |
| + t.Fatal(err) |
| + } |
| + code, _, out := rawReq(t, http.MethodPost, domainsURL(fx, "bab", "titogo"), member, `{"host":"woodstar.app","route":"go.tito.io"}`) |
| + if code != http.StatusConflict || strings.TrimSpace(out) != `{"error":"hostname unavailable"}` { |
| + t.Fatalf("attach over another holder's first-route claim = %d %s, want the neutral 409", code, out) |
| + } |
| + if c := claimOf(t, fx, "woodstar.app"); c.Account != "cdef" || c.Route != "" { |
| + t.Fatalf("holder's claim changed: %+v", *c) |
| + } |
| +} |