package api import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/metazla/meta-core/internal/claim" ) func newUDLTestServer(t *testing.T) *Server { t.Helper() return &Server{storage: newTestStorage(t)} } func udlGet(t *testing.T, s *Server, uid, cid, key string) UDLRecordResponse { t.Helper() req := httptest.NewRequest(http.MethodGet, "/api/udl/record?uid="+uid+"&cid="+cid+"&key="+key, nil) rr := httptest.NewRecorder() s.handleUDLRecordGet(rr, req) if rr.Code != http.StatusOK { t.Fatalf("GET record = %d %s", rr.Code, rr.Body) } var resp UDLRecordResponse if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { t.Fatal(err) } return resp } func udlPutBatch(t *testing.T, s *Server, claims ...claim.Claim) UDLPutBatchResponse { t.Helper() body, _ := json.Marshal(UDLPutBatchRequest{Records: claims}) req := httptest.NewRequest(http.MethodPut, "/api/udl/records", strings.NewReader(string(body))) rr := httptest.NewRecorder() s.handleUDLRecordsPut(rr, req) if rr.Code != http.StatusOK { t.Fatalf("PUT records = %d %s", rr.Code, rr.Body) } var resp UDLPutBatchResponse if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { t.Fatal(err) } return resp } // The facade stores and returns MMUR2 claims: public values in clear, // private ones as ciphertext, tombstones with their ts. func TestUDLFacadeRoundTrip(t *testing.T) { s := newUDLTestServer(t) me := testSigner(t, 7) res := udlPutBatch(t, s, signed(t, me, "self", "profile:name", "Ada Lovelace", 100), signed(t, me, "bagcsaaa", "like", "true", 100), signed(t, me, "bagcsaaa", "seek", []byte("ciphertext"), 100), ) for _, r := range res.Results { if !r.Accepted { t.Fatalf("not accepted: %+v", r) } } got := udlGet(t, s, me.UID, "self", "profile:name") if !got.Exists || got.Claim == nil || got.Claim.Value.Text != "Ada Lovelace" || got.Claim.Verify() != nil { t.Fatalf("profile = %+v", got) } if seek := udlGet(t, s, me.UID, "bagcsaaa", "seek"); seek.Claim == nil || seek.Claim.Value.Kind != claim.KindBytes { t.Fatalf("seek = %+v", seek) } if miss := udlGet(t, s, me.UID, "bagcsaaa", "nope"); miss.Exists || miss.Claim != nil { t.Fatalf("missing cell = %+v", miss) } // Retraction: stored, returned with its ts, no longer "exists". udlPutBatch(t, s, signed(t, me, "bagcsaaa", "like", nil, 200)) if tomb := udlGet(t, s, me.UID, "bagcsaaa", "like"); tomb.Exists || tomb.Claim == nil || tomb.Claim.TS != 200 { t.Fatalf("tombstone = %+v", tomb) } rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/udl/cid/self/aggregate?keys=profile:name", nil) req = withVars(req, map[string]string{"cid": "self"}) s.handleUDLAggregate(rr, req) if !strings.Contains(rr.Body.String(), `"sample_size":1`) { t.Fatalf("aggregate = %s", rr.Body) } } // A stale claim in a batch is reported in its slot without failing the rest. func TestUDLRecordsPut_StaleSlotDoesNotFailTheBatch(t *testing.T) { s := newUDLTestServer(t) me := testSigner(t, 7) udlPutBatch(t, s, signed(t, me, "bagcsaaa", "seen:s01e01", "true", 400)) res := udlPutBatch(t, s, signed(t, me, "bagcsaaa", "seen:s01e01", "false", 300), signed(t, me, "bagcsaaa", "seen:s01e02", "true", 300), ) if res.Results[0].Accepted || res.Results[0].Status != "stale" || !res.Results[1].Accepted { t.Fatalf("results = %+v", res.Results) } } func TestUDLRecordsPut_RejectsEmptyBatch(t *testing.T) { s := newUDLTestServer(t) req := httptest.NewRequest(http.MethodPut, "/api/udl/records", strings.NewReader(`{"records":[]}`)) rr := httptest.NewRecorder() s.handleUDLRecordsPut(rr, req) if rr.Code != http.StatusBadRequest { t.Fatalf("empty batch = %d", rr.Code) } }