package ingest import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestDecodeExportBody_IgnoresTrailingPartialNDJSONLine(t *testing.T) { body := strings.Join([]string{ `{"seq":1,"did":"did:plc:alice","cid":"cid1","operation":{"x":1}}`, `{"seq":2,"did":"did:plc:bob","cid":"cid2","operation":{"x":2}}`, `{"seq":3,"did":"did:plc:carol","cid":"cid3","operation":{"x":3}`, }, "\n") records, err := decodeExportBody(strings.NewReader(body), 0) if err != nil { t.Fatalf("decode export body: %v", err) } if len(records) != 2 { t.Fatalf("record count mismatch: got %d want 2", len(records)) } if records[0].Seq != 1 || records[1].Seq != 2 { t.Fatalf("unexpected sequences: got [%d %d], want [1 2]", records[0].Seq, records[1].Seq) } } func TestDecodeExportBody_FailsOnMalformedNonTrailingNDJSONLine(t *testing.T) { body := strings.Join([]string{ `{"seq":1,"did":"did:plc:alice","cid":"cid1","operation":{"x":1}}`, `{"seq":2,"did":"did:plc:bob","cid":"cid2","operation":{"x":2}`, `{"seq":3,"did":"did:plc:carol","cid":"cid3","operation":{"x":3}}`, }, "\n") _, err := decodeExportBody(strings.NewReader(body), 0) if err == nil { t.Fatalf("expected malformed middle line to fail") } } func TestFetchDIDLog_UsesAuditFallbackForCIDs(t *testing.T) { const did = "did:plc:alice" op1 := json.RawMessage(`{"did":"did:plc:alice","sig":"x","sigPayload":"e30","publicKey":"a"}`) op2 := json.RawMessage(`{"did":"did:plc:alice","prev":"bafy-one","sig":"x","sigPayload":"e30","publicKey":"a"}`) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/" + did + "/log": w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode([]json.RawMessage{op1, op2}) case "/" + did + "/log/audit": w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode([]map[string]any{ {"did": did, "operation": op1, "cid": "bafy-one", "createdAt": "2026-02-27T00:00:00Z"}, {"did": did, "operation": op2, "cid": "bafy-two", "createdAt": "2026-02-27T00:00:01Z"}, }) default: http.NotFound(w, r) } })) defer srv.Close() client := NewClient(srv.URL) records, err := client.FetchDIDLog(context.Background(), did) if err != nil { t.Fatalf("fetch did log: %v", err) } if len(records) != 2 { t.Fatalf("record count mismatch: got %d want 2", len(records)) } if records[0].CID != "bafy-one" || records[1].CID != "bafy-two" { t.Fatalf("cid fallback mismatch: got [%s %s]", records[0].CID, records[1].CID) } } func TestFetchDIDLog_FailsWhenCIDsUnavailable(t *testing.T) { const did = "did:plc:bob" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/" + did + "/log": _ = json.NewEncoder(w).Encode([]json.RawMessage{json.RawMessage(`{"did":"did:plc:bob"}`)}) case "/" + did + "/log/audit": http.NotFound(w, r) default: http.NotFound(w, r) } })) defer srv.Close() client := NewClient(srv.URL) _, err := client.FetchDIDLog(context.Background(), did) if err == nil { t.Fatalf("expected cid fallback error") } }