aboutsummaryrefslogtreecommitdiff
path: root/internal/api/plc_compatibility_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/plc_compatibility_test.go')
-rw-r--r--internal/api/plc_compatibility_test.go61
1 files changed, 60 insertions, 1 deletions
diff --git a/internal/api/plc_compatibility_test.go b/internal/api/plc_compatibility_test.go
index 67cbafa..b914278 100644
--- a/internal/api/plc_compatibility_test.go
+++ b/internal/api/plc_compatibility_test.go
@@ -14,7 +14,6 @@ import (
"strings"
"testing"
"time"
-
"github.com/Fuwn/plutia/internal/checkpoint"
"github.com/Fuwn/plutia/internal/config"
"github.com/Fuwn/plutia/internal/ingest"
@@ -24,28 +23,37 @@ import (
func TestPLCCompatibilityGetDIDMatchesStoredDocument(t *testing.T) {
ts, store, _, cleanup := newCompatibilityServer(t)
+
defer cleanup()
state, ok, err := store.GetState("did:plc:alice")
+
if err != nil {
t.Fatalf("get state: %v", err)
}
+
if !ok {
t.Fatalf("state not found")
}
resp, err := http.Get(ts.URL + "/did:plc:alice")
+
if err != nil {
t.Fatalf("get did: %v", err)
}
+
defer resp.Body.Close()
+
if resp.StatusCode != http.StatusOK {
t.Fatalf("status: got %d want 200", resp.StatusCode)
}
+
if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "application/did+ld+json") {
t.Fatalf("content-type mismatch: %s", got)
}
+
body, _ := io.ReadAll(resp.Body)
+
if strings.TrimSpace(string(body)) != strings.TrimSpace(string(state.DIDDocument)) {
t.Fatalf("did document mismatch\n got: %s\nwant: %s", string(body), string(state.DIDDocument))
}
@@ -53,26 +61,35 @@ func TestPLCCompatibilityGetDIDMatchesStoredDocument(t *testing.T) {
func TestPLCCompatibilityGetLogOrdered(t *testing.T) {
ts, _, recs, cleanup := newCompatibilityServer(t)
+
defer cleanup()
resp, err := http.Get(ts.URL + "/did:plc:alice/log")
+
if err != nil {
t.Fatalf("get log: %v", err)
}
+
defer resp.Body.Close()
+
if resp.StatusCode != http.StatusOK {
t.Fatalf("status: got %d want 200", resp.StatusCode)
}
+
var ops []map[string]any
+
if err := json.NewDecoder(resp.Body).Decode(&ops); err != nil {
t.Fatalf("decode log: %v", err)
}
+
if len(ops) != 2 {
t.Fatalf("log length mismatch: got %d want 2", len(ops))
}
+
if _, ok := ops[0]["prev"]; ok {
t.Fatalf("first op should be genesis without prev")
}
+
if prev, _ := ops[1]["prev"].(string); prev != recs[0].CID {
t.Fatalf("second op prev mismatch: got %q want %q", prev, recs[0].CID)
}
@@ -80,29 +97,39 @@ func TestPLCCompatibilityGetLogOrdered(t *testing.T) {
func TestPLCCompatibilityExportCount(t *testing.T) {
ts, _, _, cleanup := newCompatibilityServer(t)
+
defer cleanup()
resp, err := http.Get(ts.URL + "/export?count=2")
+
if err != nil {
t.Fatalf("get export: %v", err)
}
+
defer resp.Body.Close()
+
if resp.StatusCode != http.StatusOK {
t.Fatalf("status: got %d want 200", resp.StatusCode)
}
+
if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "application/jsonlines") {
t.Fatalf("content-type mismatch: %s", got)
}
+
body, _ := io.ReadAll(resp.Body)
lines := strings.Split(strings.TrimSpace(string(body)), "\n")
+
if len(lines) != 2 {
t.Fatalf("line count mismatch: got %d want 2", len(lines))
}
+
for _, line := range lines {
var entry map[string]any
+
if err := json.Unmarshal([]byte(line), &entry); err != nil {
t.Fatalf("decode export line: %v", err)
}
+
for _, key := range []string{"did", "operation", "cid", "nullified", "createdAt"} {
if _, ok := entry[key]; !ok {
t.Fatalf("missing export key %q in %v", key, entry)
@@ -113,20 +140,27 @@ func TestPLCCompatibilityExportCount(t *testing.T) {
func TestPLCCompatibilityPostIsMethodNotAllowed(t *testing.T) {
ts, _, _, cleanup := newCompatibilityServer(t)
+
defer cleanup()
req, err := http.NewRequest(http.MethodPost, ts.URL+"/did:plc:alice", strings.NewReader(`{}`))
+
if err != nil {
t.Fatalf("new request: %v", err)
}
+
resp, err := http.DefaultClient.Do(req)
+
if err != nil {
t.Fatalf("post did: %v", err)
}
+
defer resp.Body.Close()
+
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatalf("status: got %d want 405", resp.StatusCode)
}
+
if allow := resp.Header.Get("Allow"); allow != http.MethodGet {
t.Fatalf("allow header mismatch: got %q want %q", allow, http.MethodGet)
}
@@ -134,14 +168,19 @@ func TestPLCCompatibilityPostIsMethodNotAllowed(t *testing.T) {
func TestPLCCompatibilityNoVerificationMetadataLeak(t *testing.T) {
ts, _, _, cleanup := newCompatibilityServer(t)
+
defer cleanup()
resp, err := http.Get(ts.URL + "/did:plc:alice")
+
if err != nil {
t.Fatalf("get did: %v", err)
}
+
defer resp.Body.Close()
+
body, _ := io.ReadAll(resp.Body)
+
if strings.Contains(string(body), "checkpoint_reference") {
t.Fatalf("compatibility endpoint leaked verification metadata: %s", string(body))
}
@@ -149,48 +188,63 @@ func TestPLCCompatibilityNoVerificationMetadataLeak(t *testing.T) {
func TestPLCCompatibilityProofEndpointStillWorks(t *testing.T) {
ts, _, _, cleanup := newCompatibilityServer(t)
+
defer cleanup()
resp, err := http.Get(ts.URL + "/did/did:plc:alice/proof")
+
if err != nil {
t.Fatalf("get proof: %v", err)
}
+
defer resp.Body.Close()
+
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
+
t.Fatalf("proof status: got %d want 200 body=%s", resp.StatusCode, string(body))
}
}
func newCompatibilityServer(t *testing.T) (*httptest.Server, *storage.PebbleStore, []types.ExportRecord, func()) {
t.Helper()
+
tmp := t.TempDir()
dataDir := filepath.Join(tmp, "data")
+
if err := os.MkdirAll(dataDir, 0o755); err != nil {
t.Fatalf("mkdir data: %v", err)
}
seed := make([]byte, ed25519.SeedSize)
+
if _, err := rand.Read(seed); err != nil {
t.Fatalf("seed: %v", err)
}
+
keyPath := filepath.Join(tmp, "mirror.key")
+
if err := os.WriteFile(keyPath, []byte(base64.RawURLEncoding.EncodeToString(seed)), 0o600); err != nil {
t.Fatalf("write key: %v", err)
}
recs := buildCheckpointScenarioRecords(t)
sourcePath := filepath.Join(tmp, "records.ndjson")
+
writeRecordsFile(t, sourcePath, recs)
store, err := storage.OpenPebble(dataDir)
+
if err != nil {
t.Fatalf("open pebble: %v", err)
}
+
if err := store.SetMode(config.ModeMirror); err != nil {
t.Fatalf("set mode: %v", err)
}
+
bl, err := storage.OpenBlockLog(dataDir, 3, 4)
+
if err != nil {
t.Fatalf("open block log: %v", err)
}
@@ -212,12 +266,15 @@ func newCompatibilityServer(t *testing.T) (*httptest.Server, *storage.PebbleStor
}
cpMgr := checkpoint.NewManager(store, dataDir, keyPath)
svc := ingest.NewService(cfg, store, ingest.NewClient(sourcePath), bl, cpMgr)
+
if err := svc.Replay(context.Background()); err != nil {
t.Fatalf("replay: %v", err)
}
+
if err := svc.Flush(context.Background()); err != nil {
t.Fatalf("flush: %v", err)
}
+
if _, err := svc.Snapshot(context.Background()); err != nil {
t.Fatalf("snapshot: %v", err)
}
@@ -226,7 +283,9 @@ func newCompatibilityServer(t *testing.T) (*httptest.Server, *storage.PebbleStor
cleanup := func() {
ts.Close()
svc.Close()
+
_ = store.Close()
}
+
return ts, store, recs, cleanup
}