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/1.5.0" { 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) } } 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") } } } 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") } }