package console import ( "context" "encoding/json" "io" "net/http" "os" "testing" lsfile "github.com/benbjohnson/litestream/file" "github.com/carlosframework/platform/internal/dbdoor" "github.com/carlosframework/platform/internal/dbdoor/dbdoortest" ) // The console's own --from restore runs dbdoor's planner and restore over // replicaDoer, and gets exactly the copy the plan promised. func TestReplicaDoerRestoresLikeTheDoor(t *testing.T) { root := t.TempDir() dbdoortest.Replicate(t, root, 25) c := dbdoor.NewClient(replicaDoer(lsfile.NewReplicaClient(root))) plan, err := dbdoor.PlanRestore(context.Background(), c, 0) if err != nil { t.Fatal(err) } r, err := dbdoor.RestoreToTarget(context.Background(), c, t.TempDir(), 0) if err != nil { t.Fatal(err) } defer os.RemoveAll(r.Dir) st, err := os.Stat(r.Path) if err != nil { t.Fatal(err) } if st.Size() != plan.RestoredBytes || r.TXID != plan.TXID { t.Fatalf("restored %d at %d; plan %+v", st.Size(), r.TXID, plan) } } // The status codes the client reads: a coordinate that is not one is a // 404, an empty level is an empty list, a file that is not there is a 404 // (the client's os.ErrNotExist), and a range the door cannot serve is a 416. func TestReplicaDoerAnswersLikeTheDoor(t *testing.T) { root := t.TempDir() dbdoortest.Replicate(t, root, 2) do := replicaDoer(lsfile.NewReplicaClient(root)) l0, err := listReplicaLevel(context.Background(), lsfile.NewReplicaClient(root), 0) if err != nil || len(l0.Files) == 0 { t.Fatalf("level 0 = %+v, %v; want files", l0, err) } span := "/ltx/0/" + dbdoor.Span(l0.Files[0].MinTXID, l0.Files[0].MaxTXID) for _, c := range []struct { tail, rng string want int }{ {"/nope/0", "", http.StatusNotFound}, {"/ltx/", "", http.StatusNotFound}, {"/ltx/10", "", http.StatusNotFound}, {span + "/extra", "", http.StatusNotFound}, {"/ltx/0/not-a-span", "", http.StatusNotFound}, {"/ltx/0/900-901", "bytes=0-", http.StatusNotFound}, {span, "items=0-", http.StatusRequestedRangeNotSatisfiable}, {span, "bytes=0-", http.StatusPartialContent}, {span, "", http.StatusOK}, {"/ltx/5", "", http.StatusOK}, } { h := http.Header{} if c.rng != "" { h.Set("Range", c.rng) } res, err := do(context.Background(), c.tail, h) if err != nil { t.Fatalf("%s %q: %v", c.tail, c.rng, err) } body, _ := io.ReadAll(res.Body) res.Body.Close() if res.StatusCode != c.want { t.Errorf("%s %q = %d, want %d", c.tail, c.rng, res.StatusCode, c.want) } if c.tail == "/ltx/5" { var list dbdoor.ListResponse if err := json.Unmarshal(body, &list); err != nil || list.Files == nil || len(list.Files) != 0 { t.Errorf("empty level = %s (%v), want an empty list", body, err) } } } }