package aerecloud // Probele clientului Go, pe un server httptest LOCAL: ce trimite clientul (metoda, cale, cheia, corpul, user-agent-ul) si ce // face cu fiecare raspuns. Fiecare proba are perechea ei negativa; controlul negativ pe paznici e control-negativ.mjs. // go test ./... import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" ) type vazut struct{ Method, Path, Key, Body, UA string } type raspunsProba struct { status int body []byte headers map[string]string } type server struct { mu sync.Mutex vazute []vazut raspunsuri map[string]raspunsProba srv *httptest.Server } func porneste(t *testing.T) (*server, *Client) { s := &server{raspunsuri: map[string]raspunsProba{}} s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) p := r.URL.EscapedPath() if r.URL.RawQuery != "" { p += "?" + r.URL.RawQuery } s.mu.Lock() s.vazute = append(s.vazute, vazut{r.Method, p, r.Header.Get("x-api-key"), string(b), r.Header.Get("user-agent")}) rp, ok := s.raspunsuri[r.URL.EscapedPath()] s.mu.Unlock() if !ok { rp = raspunsProba{200, []byte("{}"), nil} } for k, v := range rp.headers { w.Header().Set(k, v) } w.WriteHeader(rp.status) w.Write(rp.body) })) t.Cleanup(s.srv.Close) c, err := NewWithBase("ak2800.0xAB.SINTETIC", s.srv.URL+"/v1", nil) if err != nil { t.Fatal(err) } return s, c } func (s *server) ultima() vazut { s.mu.Lock(); defer s.mu.Unlock(); return s.vazute[len(s.vazute)-1] } func (s *server) pune(p string, st int, body []byte, h map[string]string) { s.mu.Lock() s.raspunsuri[p] = raspunsProba{st, body, h} s.mu.Unlock() } var ctx = context.Background() func TestCheiaEObligatorie(t *testing.T) { if _, err := New(""); err == nil { t.Fatal("o cheie goala nu a fost refuzata") } } func TestReadinessCheiaNumaiLaAttest(t *testing.T) { s, c := porneste(t) if _, err := c.Readiness(ctx, "example.com", false, false); err != nil { t.Fatal(err) } v := s.ultima() if v.Method != "POST" || v.Path != "/v1/pq/readiness" || v.Key != "" || v.Body != `{"domain":"example.com"}` { t.Fatalf("fara attest: %+v", v) } if v.UA != "aere-cloud-go/"+Version { // versiunea din cod, nu scrisa fix (2026-09-26: 1.5.2 a rupt literalul) t.Fatalf("user-agent: %q", v.UA) } c.Readiness(ctx, "example.com", true, true) v = s.ultima() if v.Key != "ak2800.0xAB.SINTETIC" || v.Body != `{"attest":true,"domain":"example.com","fresh":true}` { t.Fatalf("cu attest: %+v", v) } } func TestPerimetruFinalitateDovada(t *testing.T) { s, c := porneste(t) f := false c.ReadinessPerimeter(ctx, "example.com", PerimeterOptions{Hosts: []string{"api", `www"x`}, Discover: &f, Attest: true}) v := s.ultima() var b map[string]any if err := json.Unmarshal([]byte(v.Body), &b); err != nil || b["discover"] != false || b["attest"] != true || len(b["hosts"].([]any)) != 2 || v.Key == "" { t.Fatalf("perimetru: %+v", v) } c.PQFinality(ctx, "latest", "testnet", "both") if v = s.ultima(); v.Path != "/v1/pq/finality/latest?network=testnet&client=both" || v.Key != "" { t.Fatalf("finalitate: %+v", v) } c.Proof(ctx, "0xab") if v = s.ultima(); v.Path != "/v1/proof/0xab" || v.Key == "" { t.Fatalf("dovada: %+v", v) } c.Verify(ctx, json.RawMessage(`{"statementHash":"0x00"}`), 28001) if v = s.ultima(); v.Method != "POST" || v.Path != "/v1/verify?chain=28001" || v.Key == "" || v.Body != `{"statementHash":"0x00"}` { t.Fatalf("verify: %+v", v) } c.Verify(ctx, json.RawMessage(`{}`), 0) if v = s.ultima(); v.Path != "/v1/verify" { t.Fatalf("verify fara lant: %+v", v) } } var zi = []byte("{\"t\":\"2026-05-01T10:00:00.000Z\",\"m\":\"GET\",\"p\":\"/v1/account\",\"s\":200}\n") func TestExportulZilei(t *testing.T) { s, c := porneste(t) d := sha256Hex(zi) s.pune("/v1/account/audit", 200, zi, map[string]string{"x-aere-digest": d, "x-aere-day": "2026-05-01", "x-aere-entries": "1"}) e, err := c.AuditExport(ctx, "2026-05-01", "jsonl") if err != nil || e.Verified == nil || !*e.Verified || e.Entries != 1 || e.Day != "2026-05-01" || string(e.Bytes) != string(zi) { t.Fatalf("jsonl: %+v %v", e, err) } if v := s.ultima(); v.Path != "/v1/account/audit?format=jsonl&day=2026-05-01" { t.Fatalf("cale: %s", v.Path) } s.pune("/v1/account/audit", 200, []byte("alti octeti"), map[string]string{"x-aere-digest": d}) if e, _ = c.AuditExport(ctx, "2026-05-01", "jsonl"); e.Verified == nil || *e.Verified { t.Fatal("octeti straini nu au voie sa iasa verificati") } if e, _ = c.AuditExport(ctx, "2026-05-01", "cef"); e.Verified != nil { t.Fatal("cef nu pretinde verificare") } s.pune("/v1/account/audit", 400, []byte(`{"error":"bad_format"}`), nil) var ae *Error if _, err = c.AuditExport(ctx, "", "xml"); !errors.As(err, &ae) || ae.Status != 400 { t.Fatalf("400 neridicat: %v", err) } } func TestExportulPastrat(t *testing.T) { s, c := porneste(t) d := sha256Hex(zi) s.pune("/v1/account/audit/retention", 200, []byte(`{"retentionDays":90}`), nil) if r, err := c.AuditRetention(ctx); err != nil || !strings.Contains(string(r), "90") || s.ultima().Method != "GET" || s.ultima().Key == "" { t.Fatalf("retentie: %s %v", r, err) } piatra := `{"error":"audit_day_expired","expired":{"day":"2026-05-01","digest":"` + d + `","entries":1,"deletedAt":"2026-09-25T12:00:01.000Z","retentionDays":90}}` s.pune("/v1/account/audit", 410, []byte(piatra), nil) k, err := c.AuditVerifyKept(ctx, "2026-05-01", zi) if err != nil || k.State != "expired" || !k.Matches || k.Digest != d || k.DeletedAt != "2026-09-25T12:00:01.000Z" { t.Fatalf("piatra: %+v %v", k, err) } stricat := append([]byte{}, zi...) stricat[5] ^= 1 if k, _ = c.AuditVerifyKept(ctx, "2026-05-01", stricat); k.Matches { t.Fatal("un export modificat nu are voie sa se potriveasca cu piatra") } s.pune("/v1/account/audit", 200, zi, map[string]string{"x-aere-digest": d}) if k, _ = c.AuditVerifyKept(ctx, "2026-05-01", zi); k.State != "held" || !k.Matches { t.Fatalf("ziua tinuta: %+v", k) } if k, _ = c.AuditVerifyKept(ctx, "2026-05-01", stricat); k.Matches { t.Fatal("ziua tinuta: un export modificat nu se potriveste") } s.pune("/v1/account/audit", 429, []byte(`{"error":"rate_limit"}`), nil) var ae *Error if _, err = c.AuditVerifyKept(ctx, "2026-05-01", zi); !errors.As(err, &ae) || ae.Status != 429 { t.Fatalf("un 429 intors ca nepotrivire: %v", err) } } func TestRedirectareaNuEUrmata(t *testing.T) { s, c := porneste(t) // o redirectare spre alta cale a aceluiasi server: daca ar fi urmata, cheia ar ajunge acolo s.pune("/v1/account", 302, nil, map[string]string{"location": s.srv.URL + "/in-alta-parte"}) var ae *Error if _, err := c.Account(ctx); !errors.As(err, &ae) || ae.Status != 302 { t.Fatalf("302 trebuia intors ca raspuns, nu urmat: %v", err) } for _, v := range s.vazute { if strings.HasPrefix(v.Path, "/in-alta-parte") { t.Fatal("redirectarea a fost urmata") } } } // 2026-09-29: verdictul Trust API hashuieste statement-ul in ordinea LUI de chei; clientul trimite octetii plicului asa cum sunt. // Perechea negativa: acelasi plic trecut printr-un map de Go iese cu cheile sortate, adica alt text canonic. func TestVerifyPastreazaOrdinea(t *testing.T) { s, c := porneste(t) plic := `{"v":1,"statement":{"v":1,"kind":"aere-proof-of-data","subject":{"name":"x"},"createdAt":"2026-09-26T00:00:00Z"},"statementHash":"0x00"}` if _, err := c.Verify(context.Background(), json.RawMessage(plic), 0); err != nil { t.Fatal(err) } if v := s.ultima(); v.Body != plic { t.Fatalf("ordinea cheilor schimbata: %s", v.Body) } var m map[string]any _ = json.Unmarshal([]byte(plic), &m) prin, _ := json.Marshal(m) if string(prin) == plic { t.Fatal("controlul probei: un map ar fi trebuit sa sorteze cheile") } } // 1.7.0: cererea de identitate pleaca pe ruta ei, cu cheie, cu prezentarea NESCHIMBATA (un numar peste 2^53 ramane exact) func TestIdentityVerifyPastreazaOctetii(t *testing.T) { s, c := porneste(t) prez := `{"v":1,"kind":"aere-presentation","credential":{"statement":{"claims":{"id":12345678901234567891}}}}` if _, err := c.IdentityVerify(context.Background(), IdentityVerifyRequest{Presentation: json.RawMessage(prez), Audience: "https://v.example", Nonce: "n-1", TrustedIssuers: []json.RawMessage{json.RawMessage(`"aere-id:00"`)}}); err != nil { t.Fatal(err) } v := s.ultima() if v.Method != "POST" || v.Path != "/v1/identity/verify" || v.Key == "" || !strings.Contains(v.Body, `"presentation":`+prez) || !strings.Contains(v.Body, `"trustedIssuers":["aere-id:00"]`) || strings.Contains(v.Body, "statusLists") { t.Fatalf("cererea de identitate: %+v", v) } if _, err := c.ComplianceCheck(context.Background(), ComplianceCheckRequest{Presentation: json.RawMessage(prez), Policy: json.RawMessage(`{"id":"p"}`), Audience: "a", Nonce: "n", Record: true}); err != nil { t.Fatal(err) } if v = s.ultima(); v.Path != "/v1/compliance/check" || v.Key == "" || !strings.Contains(v.Body, `"presentation":`+prez) || !strings.Contains(v.Body, `"record":true`) { t.Fatalf("cererea de conformitate: %+v", v) } var m map[string]any _ = json.Unmarshal([]byte(prez), &m) prin, _ := json.Marshal(m) if strings.Contains(string(prin), "12345678901234567891") { t.Fatal("controlul probei: un map ar fi trebuit sa strice numarul mare") } } func TestVerifyWebhook(t *testing.T) { secret := "AERE-SINTETIC-secret" corp := []byte(`{"event":"pq-anchor","data":{"height":42}}`) m := hmac.New(sha256.New, []byte(secret)) m.Write(corp) bun := hex.EncodeToString(m.Sum(nil)) if !VerifyWebhook(corp, bun, secret) { t.Fatal("semnatura buna refuzata") } stricata := bun[:len(bun)-1] + map[bool]string{true: "b", false: "a"}[strings.HasSuffix(bun, "a")] if VerifyWebhook(corp, stricata, secret) || VerifyWebhook(corp, bun+"x", secret) || VerifyWebhook(append(corp, ' '), bun, secret) { t.Fatal("o semnatura gresita sau un corp atins a trecut") } } // 1.8.0: cererile de plati pleaca pe rutele lor, cu cheie, cu politica si intrarile NESCHIMBATE (o suma peste 2^53 ramane exacta) func TestPaymentsPastreazaOctetii(t *testing.T) { s, c := porneste(t) pol := `{"v":1,"kind":"aere-payment-risk-policy","id":"p"}` in := `{"chainId":28001,"amount":12345678901234567891,"asset":"native"}` if _, err := c.PaymentsRisk(context.Background(), PaymentsRiskRequest{Policy: json.RawMessage(pol), Inputs: json.RawMessage(in)}); err != nil { t.Fatal(err) } v := s.ultima() if v.Method != "POST" || v.Path != "/v1/payments/risk" || v.Key == "" || !strings.Contains(v.Body, `"inputs":`+in) || !strings.Contains(v.Body, `"policy":`+pol) { t.Fatalf("cererea de risc: %+v", v) } if _, err := c.PaymentsReceipt(context.Background(), PaymentsReceiptRequest{Policy: json.RawMessage(pol), Inputs: json.RawMessage(in), TxHash: "0x11", Purpose: "order 7"}); err != nil { t.Fatal(err) } if v = s.ultima(); v.Path != "/v1/payments/receipt" || v.Key == "" || !strings.Contains(v.Body, `"txHash":"0x11"`) || !strings.Contains(v.Body, `"purpose":"order 7"`) || !strings.Contains(v.Body, `"inputs":`+in) { t.Fatalf("cererea de chitanta: %+v", v) } rec := `{"instruction":{"amount":"12345678901234567891"},"envelope":{"v":1}}` if _, err := c.PaymentsVerify(context.Background(), PaymentsVerifyRequest{Receipt: json.RawMessage(rec), Policy: json.RawMessage(pol), Inputs: json.RawMessage(in)}); err != nil { t.Fatal(err) } if v = s.ultima(); v.Path != "/v1/payments/verify" || v.Key == "" || !strings.Contains(v.Body, `"receipt":`+rec) { t.Fatalf("cererea de verificare: %+v", v) } }