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 "encoding/json"
6 "io"
7 "net/http"
8 "os"
9 "testing"
10
11 lsfile "github.com/benbjohnson/litestream/file"
12
13 "github.com/carlosframework/platform/internal/dbdoor"
14 "github.com/carlosframework/platform/internal/dbdoor/dbdoortest"
15)
16
17// The console's own --from restore runs dbdoor's planner and restore over
18// replicaDoer, and gets exactly the copy the plan promised.
19func TestReplicaDoerRestoresLikeTheDoor(t *testing.T) {
20 root := t.TempDir()
21 dbdoortest.Replicate(t, root, 25)
22 c := dbdoor.NewClient(replicaDoer(lsfile.NewReplicaClient(root)))
23 plan, err := dbdoor.PlanRestore(context.Background(), c, 0)
24 if err != nil {
25 t.Fatal(err)
26 }
27 r, err := dbdoor.RestoreToTarget(context.Background(), c, t.TempDir(), 0)
28 if err != nil {
29 t.Fatal(err)
30 }
31 defer os.RemoveAll(r.Dir)
32 st, err := os.Stat(r.Path)
33 if err != nil {
34 t.Fatal(err)
35 }
36 if st.Size() != plan.RestoredBytes || r.TXID != plan.TXID {
37 t.Fatalf("restored %d at %d; plan %+v", st.Size(), r.TXID, plan)
38 }
39}
40
41// The status codes the client reads: a coordinate that is not one is a
42// 404, an empty level is an empty list, a file that is not there is a 404
43// (the client's os.ErrNotExist), and a range the door cannot serve is a 416.
44func TestReplicaDoerAnswersLikeTheDoor(t *testing.T) {
45 root := t.TempDir()
46 dbdoortest.Replicate(t, root, 2)
47 do := replicaDoer(lsfile.NewReplicaClient(root))
48 l0, err := listReplicaLevel(context.Background(), lsfile.NewReplicaClient(root), 0)
49 if err != nil || len(l0.Files) == 0 {
50 t.Fatalf("level 0 = %+v, %v; want files", l0, err)
51 }
52 span := "/ltx/0/" + dbdoor.Span(l0.Files[0].MinTXID, l0.Files[0].MaxTXID)
53 for _, c := range []struct {
54 tail, rng string
55 want int
56 }{
57 {"/nope/0", "", http.StatusNotFound},
58 {"/ltx/", "", http.StatusNotFound},
59 {"/ltx/10", "", http.StatusNotFound},
60 {span + "/extra", "", http.StatusNotFound},
61 {"/ltx/0/not-a-span", "", http.StatusNotFound},
62 {"/ltx/0/900-901", "bytes=0-", http.StatusNotFound},
63 {span, "items=0-", http.StatusRequestedRangeNotSatisfiable},
64 {span, "bytes=0-", http.StatusPartialContent},
65 {span, "", http.StatusOK},
66 {"/ltx/5", "", http.StatusOK},
67 } {
68 h := http.Header{}
69 if c.rng != "" {
70 h.Set("Range", c.rng)
71 }
72 res, err := do(context.Background(), c.tail, h)
73 if err != nil {
74 t.Fatalf("%s %q: %v", c.tail, c.rng, err)
75 }
76 body, _ := io.ReadAll(res.Body)
77 res.Body.Close()
78 if res.StatusCode != c.want {
79 t.Errorf("%s %q = %d, want %d", c.tail, c.rng, res.StatusCode, c.want)
80 }
81 if c.tail == "/ltx/5" {
82 var list dbdoor.ListResponse
83 if err := json.Unmarshal(body, &list); err != nil || list.Files == nil || len(list.Files) != 0 {
84 t.Errorf("empty level = %s (%v), want an empty list", body, err)
85 }
86 }
87 }
88}
89