carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "errors"
6 "net/http"
7 "os"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/dbdoor"
13 "github.com/carlosframework/platform/internal/lease/leasetest"
14 "github.com/carlosframework/platform/internal/provision"
15)
16
17func TestResolveDBSource(t *testing.T) {
18 fx, _ := instanceFixture(t, "code-db-source")
19 if _, err := fx.st.CreateAppIn(OpsAccountID, "shop", "paul@keymail.dev", time.Now()); err != nil {
20 t.Fatal(err)
21 }
22 m := provision.Manifest{Schema: 1, App: "shop", Domain: "shop.example.com",
23 Channel: "stable", Regions: []string{"ie"}, Seed: []string{"seed"}}
24 enableInstances(t, fx, "shop", m)
25 fx.srv.cfg.Region = "ie"
26 fx.srv.cfg.HibernatePrefix = "instances"
27 acct, err := fx.st.AccountBySqid("bab")
28 if err != nil {
29 t.Fatal(err)
30 }
31 ctx := context.Background()
32 put := func(host string, mut func(*provision.Record)) {
33 rec := provision.Record{Schema: 1, Host: host, Slug: strings.Split(host, ".")[0], App: "shop", Account: "bab",
34 Region: "ie", Channel: "stable", CreatedAt: time.Now().UTC()}
35 if mut != nil {
36 mut(&rec)
37 }
38 if err := provision.CreateRecord(ctx, fx.rel, rec); err != nil {
39 t.Fatal(err)
40 }
41 }
42 put("acme.shop.example.com", nil)
43 put("pooled.shop.example.com", func(r *provision.Record) { r.Pool = "shared" })
44 put("far.shop.example.com", func(r *provision.Record) { r.Region = "uk" })
45 put("addr.shop.example.com", func(r *provision.Record) { r.Addr = "127.0.0.1:9000" })
46
47 src, err := fx.srv.resolveDBSource(ctx, acct, "alice@keymail.dev", "shop", "acme.shop.example.com")
48 if err != nil || src.prefix != "instances/acme.shop.example.com" || src.rec.Host != "acme.shop.example.com" {
49 t.Fatalf("acme = %+v, %v", src, err)
50 }
51 for host, want := range map[string]error{
52 "nope.shop.example.com": os.ErrNotExist,
53 "pooled.shop.example.com": ErrPooled,
54 "far.shop.example.com": ErrResidency,
55 "addr.shop.example.com": errSourceClass,
56 "not a host": ErrBadHost,
57 } {
58 if _, err := fx.srv.resolveDBSource(ctx, acct, "alice@keymail.dev", "shop", host); !errors.Is(err, want) {
59 t.Errorf("%s: err = %v, want %v", host, err, want)
60 }
61 }
62}
63
64// A claim-read failure is wrapped in errSourceClaim and also kept whole on
65// dbSource.claimErr: errors.Unwrap of a two-%w wrap is nil, and
66// dbClaimFailure needs the original to pick its answer.
67func TestResolveDBSourceClaimReadError(t *testing.T) {
68 fx := dbFixture(t)
69 fx.srv.cfg.HomeRegion = "ie"
70 fx.srv.cfg.HostClaim = true
71 fx.srv.cfg.DBVersions = leasetest.NewVersionedBucket()
72 acct, err := fx.st.AccountBySqid("bab")
73 if err != nil {
74 t.Fatal(err)
75 }
76 app := titoManifest().App
77 src, err := fx.srv.resolveDBSource(t.Context(), acct, "alice@keymail.dev", app, dbTestHost)
78 if !errors.Is(err, errSourceClaim) {
79 t.Fatalf("err = %v, want errSourceClaim", err)
80 }
81 if src.claimErr == nil || !errors.Is(src.claimErr, errDBNotAdopted) {
82 t.Fatalf("claimErr = %v, want errDBNotAdopted", src.claimErr)
83 }
84 if errors.Unwrap(err) != nil {
85 t.Fatalf("errors.Unwrap(err) = %v, expected nil for a two-%%w wrap", errors.Unwrap(err))
86 }
87
88 // Through the door: the original error reaches dbClaimFailure, which
89 // answers a missing adoption with a 409. A nil error would be a 502.
90 status, out := getRaw(t, fx.url(dbdoor.ListPath("bab", app, dbTestHost, 0)), fx.member)
91 if status != http.StatusConflict || !strings.Contains(string(out), errDBNotAdopted.Error()) {
92 t.Fatalf("door answered %d %q, want 409 with %q", status, out, errDBNotAdopted)
93 }
94}
95