package crm import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestCanonicalJSONAndSignature(t *testing.T) { draft := LeadDraft{ Name: "Иван Иванов", Phone: "+79991234567", Comment: "Тестовая запись", SourceLabel: "Сайт", } canon, err := CanonicalJSON(draft) if err != nil { t.Fatalf("CanonicalJSON failed: %v", err) } // Verify that keys are sorted in canonical JSON expectedPrefix := `{"comment":"Тестовая запись","name":"Иван Иванов","phone":"+79991234567","sourceLabel":"Сайт"}` if canon != expectedPrefix { t.Errorf("CanonicalJSON got %s, want %s", canon, expectedPrefix) } secret := "test-secret-key-123" timestamp := "1729080000000" idempotencyKey := "idemp-key-12345" sig := SignPayload(secret, timestamp, idempotencyKey, canon) if len(sig) != 64 { t.Errorf("expected 64-char hex signature, got %s", sig) } // Manual check h := sha256.Sum256([]byte(canon)) ph := hex.EncodeToString(h[:]) mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(timestamp + "." + idempotencyKey + "." + ph)) expectedSig := hex.EncodeToString(mac.Sum(nil)) if sig != expectedSig { t.Errorf("signature mismatch: got %s, want %s", sig, expectedSig) } } func TestClientCreateLeadSuccess(t *testing.T) { secret := "secret123" expectedDealID := "deal_xyz987" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/public/leads" { t.Errorf("unexpected path: %s", r.URL.Path) http.NotFound(w, r) return } if r.Method != http.MethodPost { t.Errorf("unexpected method: %s", r.Method) w.WriteHeader(http.StatusMethodNotAllowed) return } timestamp := r.Header.Get("x-gt-timestamp") idempotencyKey := r.Header.Get("x-gt-idempotency-key") sig := r.Header.Get("x-gt-signature") if timestamp == "" || idempotencyKey == "" || sig == "" { t.Errorf("missing headers: timestamp=%s, idemp=%s, sig=%s", timestamp, idempotencyKey, sig) } var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Errorf("failed to decode body: %v", err) w.WriteHeader(http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(LeadResponse{ OK: true, DealID: expectedDealID, }) })) defer ts.Close() client := New(ts.URL, secret) if !client.Configured() { t.Fatal("client must be configured") } dealID, err := client.CreateLead(context.Background(), LeadDraft{ Name: "Анна Петрова", Phone: "+79281112233", }, "test-key-001") if err != nil { t.Fatalf("CreateLead failed: %v", err) } if dealID != expectedDealID { t.Errorf("got dealID %s, want %s", dealID, expectedDealID) } }