aere-cloud-sdk/go/aerecloud_test.go
Liviu 983f93aa66 1.5.2: finality() in all four clients (JS, Python, Go, Java): GET /v1/data/finality
The post-quantum finality point of the chain (finalized/safe = the parent of the
most recent anchor whose certificate reaches the threshold in force). Same surface
in every client; tests updated (JS 12, Python 12, Go ok, Java 7). Synced byte for
byte from the source tree (aerenew/sdk) on 2026-09-26.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 15:35:24 +03:00

207 lines
7.2 KiB
Go

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)
}
}
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")
}
}