package dbdoor import ( "context" "encoding/json" "io" "net/http" "strconv" "strings" "testing" "github.com/superfly/ltx" ) func TestHandleProtocolCompatibility(t *testing.T) { for _, mode := range []string{"legacy", "pinned", "downgraded-get"} { t.Run(mode, func(t *testing.T) { c := NewClient(func(ctx context.Context, tail string, h http.Header) (*http.Response, error) { out := &http.Response{StatusCode: 200, Header: http.Header{}} if tail == ListTail(0) { list := ListResponse{Files: []LTXFile{{Level: 0, MinTXID: 1, MaxTXID: 1, Size: 3}}} if mode != "legacy" { list.Protocol = 2 list.Files[0].Handle = "opaque" } b, _ := json.Marshal(list) out.Body = io.NopCloser(strings.NewReader(string(b))) } else { want := "" if mode != "legacy" { want = "opaque" } if h.Get(HandleHeader) != want || h.Get("Range") != "bytes=1-2" { t.Fatal(tail, h) } if mode == "pinned" { out.Header.Set(HandleHeader, want) } out.Body = io.NopCloser(strings.NewReader("bc")) } return out, nil }) it, err := c.LTXFiles(t.Context(), 0, 0, false) if err != nil { t.Fatal(err) } it.Close() rc, err := c.OpenLTXFile(t.Context(), 0, ltx.TXID(1), ltx.TXID(1), 1, 2) if mode == "downgraded-get" { if err == nil { rc.Close() t.Fatal("accepted unpinned body") } return } if err != nil { t.Fatal(err) } b, err := io.ReadAll(rc) rc.Close() if err != nil || string(b) != "bc" { t.Fatal(string(b), err) } }) } } func TestHandleProtocolNeverFallsBack(t *testing.T) { c := NewClient(nil) first := ListResponse{Protocol: 2, Files: []LTXFile{{Level: 0, MinTXID: 1, MaxTXID: 1, Size: 3, Handle: "first"}}} if _, err := c.rememberFiles(0, first); err != nil { t.Fatal(err) } again := ListResponse{Protocol: 2, Files: []LTXFile{{Level: 0, MinTXID: 1, MaxTXID: 1, Size: 3, Handle: "second"}}} files, err := c.rememberFiles(0, again) if err != nil || files[0].Handle != "first" { t.Fatal(files, err) } again.Files[0].Size = 4 if _, err := c.rememberFiles(0, again); err == nil { t.Fatal("metadata changed") } if _, err := c.rememberFiles(0, ListResponse{}); err == nil { t.Fatal("listing downgraded") } if _, err := c.fileHandle(0, 2, 2); err == nil { t.Fatal("missing handle fell back") } for _, bad := range []ListResponse{{Protocol: 3}, {Protocol: 2, Files: []LTXFile{{Level: 0, MinTXID: 1, MaxTXID: 1}}}} { if _, err := NewClient(nil).rememberFiles(0, bad); err == nil { t.Fatal("bad listing", bad) } } } func TestHandleProtocolCompletePlan(t *testing.T) { lists := 0 c := NewClient(func(ctx context.Context, tail string, h http.Header) (*http.Response, error) { if strings.Count(tail, "/") == 2 { lists++ if lists != 1 { t.Fatal("relisted during one restore") } return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"protocol":2,"plan":true,"files":[{"level":9,"min_txid":1,"max_txid":11,"size":100,"handle":"root"},{"level":1,"min_txid":10,"max_txid":13,"size":100,"handle":"continuation"}]}`))}, nil } if h.Get(HandleHeader) != "continuation" { t.Fatal("lost selected handle", h) } return &http.Response{StatusCode: 206, Header: h.Clone(), Body: io.NopCloser(strings.NewReader("bytes"))}, nil }) for _, level := range []int{9, 8, 1, 0, 9} { it, err := c.LTXFiles(t.Context(), level, 0, false) if err != nil { t.Fatal(err) } var count int for it.Next() { count++ if it.Item().Level != level { t.Fatal(it.Item()) } } if err := it.Close(); err != nil { t.Fatal(err) } want := 0 if level == 9 || level == 1 { want = 1 } if count != want { t.Fatalf("level %d: %d files, want %d", level, count, want) } } rc, err := c.OpenLTXFile(t.Context(), 1, 10, 13, 0, 0) if err != nil { t.Fatal(err) } rc.Close() } func TestHandleProtocolRetainsSnapshotCRC(t *testing.T) { for _, first := range []string{"0", "12345678901234567890"} { t.Run(first, func(t *testing.T) { c := NewClient(nil) for _, crc := range []string{first, "7", "null"} { var list ListResponse raw := `{"protocol":2,"files":[{"level":9,"min_txid":1,"max_txid":11,"size":100,"handle":"` + crc + `","snapshot_crc64":` + crc + `}]}` if err := json.Unmarshal([]byte(raw), &list); err != nil { t.Fatal(err) } files, err := c.rememberFiles(9, list) if err != nil { t.Fatal(err) } if files[0].Handle != first || files[0].SnapshotCRC64 == nil { t.Fatalf("lost first handle metadata: %+v", files[0]) } want, err := strconv.ParseUint(first, 10, 64) if err != nil { t.Fatal(err) } if *files[0].SnapshotCRC64 != want { t.Fatalf("CRC changed to %d", *files[0].SnapshotCRC64) } } }) } }