diff --git a/connect/go.mod b/connect/go.mod index 937bfb83..118a6acf 100644 --- a/connect/go.mod +++ b/connect/go.mod @@ -4,12 +4,15 @@ go 1.25.0 toolchain go1.25.13 -require github.com/jackc/pgx/v5 v5.9.2 +require ( + gitea.perlbach24.de/scripte/nexarch/common v0.1.0 + github.com/jackc/pgx/v5 v5.9.2 + golang.org/x/text v0.29.0 +) require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect ) diff --git a/connect/go.sum b/connect/go.sum index f5b2410e..a5cc3c17 100644 --- a/connect/go.sum +++ b/connect/go.sum @@ -1,3 +1,5 @@ +gitea.perlbach24.de/scripte/nexarch/common v0.1.0 h1:biNzFr2LOMZC7b1leY3smQtrMmSMp6IW80OvR4LSjLY= +gitea.perlbach24.de/scripte/nexarch/common v0.1.0/go.mod h1:CiEImG++QMt1xZqSMeDJhPiB7sfWTLZOwjS6OY/Iguc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/connect/internal/archiveclient/archiveclient.go b/connect/internal/archiveclient/archiveclient.go index 9027a81f..58cb2a3f 100644 --- a/connect/internal/archiveclient/archiveclient.go +++ b/connect/internal/archiveclient/archiveclient.go @@ -12,7 +12,7 @@ import ( "fmt" "net/http" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) type Client struct { diff --git a/connect/internal/auditclient/auditclient.go b/connect/internal/auditclient/auditclient.go deleted file mode 100644 index bebdfb75..00000000 --- a/connect/internal/auditclient/auditclient.go +++ /dev/null @@ -1,71 +0,0 @@ -// Package auditclient ist ein schlanker HTTP-Client fuer Core AUD-08 -// (internal/audit, POST /audit/ingest). Connect ist ein physisch -// getrenntes Go-Modul und kann Cores internal/audit daher nicht direkt -// importieren — nur ueber dessen HTTP-Schnittstelle. Gleiches Muster wie -// archive/internal/auditclient. -package auditclient - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTPClient *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: httpclient.Default()} -} - -type ingestRequest struct { - TenantSlug string `json:"tenant_slug"` - Actor string `json:"actor"` - Action string `json:"action"` - Target string `json:"target"` - Metadata map[string]any `json:"metadata"` -} - -// Ingest meldet EIN Audit-Ereignis an Core. Jeder Fehler (Transport, -// Timeout, unerwarteter Status) wird als Fehler zurueckgegeben — der -// Aufrufer entscheidet, ob/wie er das nachbehandelt (siehe -// connect/internal/audittrail fuer den ausfalltoleranten Wrapper). -func (c *Client) Ingest(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any) error { - body, err := json.Marshal(ingestRequest{ - TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, Metadata: metadata, - }) - if err != nil { - return fmt.Errorf("auditclient: request kodieren: %w", err) - } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/audit/ingest", bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("auditclient: request bauen: %w", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Service-Token", c.ServiceToken) - - resp, err := c.httpClient().Do(req) - if err != nil { - return fmt.Errorf("auditclient: aufruf fehlgeschlagen: %w", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusAccepted { - return fmt.Errorf("auditclient: unerwarteter status %d", resp.StatusCode) - } - return nil -} - -func (c *Client) httpClient() *http.Client { - if c.HTTPClient != nil { - return c.HTTPClient - } - return httpclient.Default() -} diff --git a/connect/internal/auditclient/auditclient_test.go b/connect/internal/auditclient/auditclient_test.go deleted file mode 100644 index 0d37be35..00000000 --- a/connect/internal/auditclient/auditclient_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package auditclient - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" -) - -func TestIngest_SendsTenantSlugAndServiceToken(t *testing.T) { - var gotBody ingestRequest - var gotToken string - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gotToken = r.Header.Get("X-Service-Token") - if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { - t.Fatalf("body dekodieren: %v", err) - } - w.WriteHeader(http.StatusAccepted) - })) - defer srv.Close() - - c := New(srv.URL, "svc-token-123") - err := c.Ingest(context.Background(), "acme", "user-1", "connector.sync", "erp:kunde:42", map[string]any{"count": 3}) - if err != nil { - t.Fatalf("Ingest: %v", err) - } - - if gotToken != "svc-token-123" { - t.Fatalf("Service-Token nicht gesendet, erhalten: %q", gotToken) - } - if gotBody.TenantSlug != "acme" { - t.Fatalf("TenantSlug fehlt/falsch im Audit-Ereignis: %q", gotBody.TenantSlug) - } - if gotBody.Action != "connector.sync" { - t.Fatalf("Action falsch: %q", gotBody.Action) - } -} - -func TestIngest_UnexpectedStatus_ReturnsError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - })) - defer srv.Close() - - c := New(srv.URL, "svc-token-123") - if err := c.Ingest(context.Background(), "acme", "user-1", "connector.sync", "erp:kunde:42", nil); err == nil { - t.Fatal("erwarteter Fehler bei Status 500 blieb aus") - } -} - -func TestIngest_TransportError_ReturnsError(t *testing.T) { - c := New("http://127.0.0.1:1", "svc-token-123") - if err := c.Ingest(context.Background(), "acme", "user-1", "connector.sync", "erp:kunde:42", nil); err == nil { - t.Fatal("erwarteter Transport-Fehler blieb aus") - } -} diff --git a/connect/internal/audittrail/audittrail.go b/connect/internal/audittrail/audittrail.go index 428bc16a..e9f9b91f 100644 --- a/connect/internal/audittrail/audittrail.go +++ b/connect/internal/audittrail/audittrail.go @@ -19,7 +19,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) type Recorder struct { diff --git a/connect/internal/audittrail/audittrail_test.go b/connect/internal/audittrail/audittrail_test.go index c20708c0..b4a9d419 100644 --- a/connect/internal/audittrail/audittrail_test.go +++ b/connect/internal/audittrail/audittrail_test.go @@ -11,7 +11,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) func setupOutboxTest(t *testing.T) *pgxpool.Pool { diff --git a/connect/internal/backupconnector/backupconnector.go b/connect/internal/backupconnector/backupconnector.go index f6f50d6d..b9ea4e04 100644 --- a/connect/internal/backupconnector/backupconnector.go +++ b/connect/internal/backupconnector/backupconnector.go @@ -26,8 +26,8 @@ import ( "strings" "time" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/credential/credential_test.go b/connect/internal/credential/credential_test.go index 584523eb..adb3e0af 100644 --- a/connect/internal/credential/credential_test.go +++ b/connect/internal/credential/credential_test.go @@ -13,7 +13,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" connectcrypto "gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto" diff --git a/connect/internal/crmconnector/crmconnector.go b/connect/internal/crmconnector/crmconnector.go index 8a7ce1bf..f80022a1 100644 --- a/connect/internal/crmconnector/crmconnector.go +++ b/connect/internal/crmconnector/crmconnector.go @@ -14,8 +14,8 @@ import ( "io" "net/http" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/crypto/kekprovider.go b/connect/internal/crypto/kekprovider.go index a4b67452..b4514816 100644 --- a/connect/internal/crypto/kekprovider.go +++ b/connect/internal/crypto/kekprovider.go @@ -13,7 +13,7 @@ import ( "net/http" "net/url" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) // tenantKEKResponse mirrors Core internal/kek.tenantKEKResponse (JSON diff --git a/connect/internal/datevapi/datevapi.go b/connect/internal/datevapi/datevapi.go index 6dbb905a..5a3c9128 100644 --- a/connect/internal/datevapi/datevapi.go +++ b/connect/internal/datevapi/datevapi.go @@ -54,14 +54,14 @@ type triggerRequest struct { } type runResponse struct { - ID string `json:"id"` - PeriodStart string `json:"period_start"` - PeriodEnd string `json:"period_end"` - EntryCount int `json:"entry_count"` - RetentionClass string `json:"retention_class"` - CreatedAt string `json:"created_at"` - TransferStatus string `json:"transfer_status,omitempty"` - TransferError string `json:"transfer_error,omitempty"` + ID string `json:"id"` + PeriodStart string `json:"period_start"` + PeriodEnd string `json:"period_end"` + EntryCount int `json:"entry_count"` + RetentionClass string `json:"retention_class"` + CreatedAt string `json:"created_at"` + TransferStatus string `json:"transfer_status,omitempty"` + TransferError string `json:"transfer_error,omitempty"` } func (s *Server) handleTrigger(w http.ResponseWriter, r *http.Request, tenantSlug string) { diff --git a/connect/internal/datevexport/datevexport.go b/connect/internal/datevexport/datevexport.go index 3bff6e08..8a82f868 100644 --- a/connect/internal/datevexport/datevexport.go +++ b/connect/internal/datevexport/datevexport.go @@ -34,13 +34,13 @@ var ( // (a deliberate subset, not the full ~116-column spec — DAT-02/DAT-03 // extend format coverage, not this ticket). type Entry struct { - Amount float64 // Umsatz (ohne Soll/Haben-Kennzeichen), immer positiv - DebitCredit string // "S" (Soll) oder "H" (Haben) - Account string // Konto - CounterAccount string // Gegenkonto (ohne BU-Schlüssel) - Date time.Time // Belegdatum - Text string // Buchungstext - DocumentRef string // Belegfeld 1 (Rechnungs-/Belegnummer) + Amount float64 // Umsatz (ohne Soll/Haben-Kennzeichen), immer positiv + DebitCredit string // "S" (Soll) oder "H" (Haben) + Account string // Konto + CounterAccount string // Gegenkonto (ohne BU-Schlüssel) + Date time.Time // Belegdatum + Text string // Buchungstext + DocumentRef string // Belegfeld 1 (Rechnungs-/Belegnummer) } func (e Entry) validate() error { diff --git a/connect/internal/datevexport/datevexport_test.go b/connect/internal/datevexport/datevexport_test.go index 0d009ed7..a7135393 100644 --- a/connect/internal/datevexport/datevexport_test.go +++ b/connect/internal/datevexport/datevexport_test.go @@ -89,7 +89,7 @@ func TestExport_StructuralFormatCheck(t *testing.T) { // AC2: fehlendes Pflichtfeld im Beleg führt zur klaren Abweisung. func TestExport_MissingMandatoryField_Rejected(t *testing.T) { cases := []struct { - name string + name string break_ func(e Entry) Entry }{ {"Umsatz", func(e Entry) Entry { e.Amount = 0; return e }}, diff --git a/connect/internal/datevexport/store.go b/connect/internal/datevexport/store.go index 7382d412..6584b3ff 100644 --- a/connect/internal/datevexport/store.go +++ b/connect/internal/datevexport/store.go @@ -30,8 +30,8 @@ type Run struct { } type Store struct { - Pool *pgxpool.Pool - Archive *archiveclient.Client + Pool *pgxpool.Pool + Archive *archiveclient.Client // CallbackURL is where Archive would call back on deletion — Connect // has no export-deletion path yet (DAT-02/03), so this points at a // stub endpoint that always 501s, same pattern as other modules' diff --git a/connect/internal/datevexport/store_test.go b/connect/internal/datevexport/store_test.go index b52e9e0d..6bfc0e44 100644 --- a/connect/internal/datevexport/store_test.go +++ b/connect/internal/datevexport/store_test.go @@ -161,4 +161,3 @@ func countRuns(t *testing.T, pool *pgxpool.Pool, tenantSlug string) int { } return n } - diff --git a/connect/internal/dms2import/mapping_test.go b/connect/internal/dms2import/mapping_test.go index 9969432d..59248de0 100644 --- a/connect/internal/dms2import/mapping_test.go +++ b/connect/internal/dms2import/mapping_test.go @@ -34,7 +34,7 @@ func TestApplyMapping_UsesGivenMappingNotSuggestion(t *testing.T) { doc := SourceDocument{ PK: "1", RawFields: map[string]string{ - "title": "Originaltitel", + "title": "Originaltitel", "custom_title_field": "Korrigierter Titel", }, } diff --git a/connect/internal/dms2import/parse.go b/connect/internal/dms2import/parse.go index b289bef2..aa62ce11 100644 --- a/connect/internal/dms2import/parse.go +++ b/connect/internal/dms2import/parse.go @@ -241,4 +241,3 @@ func DetectFields(docs []SourceDocument) []string { } return order } - diff --git a/connect/internal/dms2importapi/dms2importapi.go b/connect/internal/dms2importapi/dms2importapi.go index 8df57d13..13d8983f 100644 --- a/connect/internal/dms2importapi/dms2importapi.go +++ b/connect/internal/dms2importapi/dms2importapi.go @@ -19,9 +19,9 @@ import ( // assistant in fieldmapping.Store — one mapping per tenant, since a // tenant realistically migrates from one third-party system at a time. const ( - connectorName = "dms2_import" - connectorInstanceID = "paperless_ngx" - maxUploadBytes int64 = 200 << 20 // 200 MiB: generous for a test/migration export, not unbounded + connectorName = "dms2_import" + connectorInstanceID = "paperless_ngx" + maxUploadBytes int64 = 200 << 20 // 200 MiB: generous for a test/migration export, not unbounded ) type Server struct { @@ -55,9 +55,9 @@ func (s *Server) requireIdentity(next func(w http.ResponseWriter, r *http.Reques } type previewResponse struct { - DocumentCount int `json:"document_count"` - AvailableFields []string `json:"available_fields"` - SuggestedMapping []fieldmapping.Entry `json:"suggested_mapping"` + DocumentCount int `json:"document_count"` + AvailableFields []string `json:"available_fields"` + SuggestedMapping []fieldmapping.Entry `json:"suggested_mapping"` Schema []fieldmapping.FieldDef `json:"schema"` } diff --git a/connect/internal/erpconnector/erpconnector.go b/connect/internal/erpconnector/erpconnector.go index 2b421225..ad8698a3 100644 --- a/connect/internal/erpconnector/erpconnector.go +++ b/connect/internal/erpconnector/erpconnector.go @@ -14,8 +14,8 @@ import ( "io" "net/http" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/erpexport/store.go b/connect/internal/erpexport/store.go index 3e80fd15..91c2ca1a 100644 --- a/connect/internal/erpexport/store.go +++ b/connect/internal/erpexport/store.go @@ -49,16 +49,16 @@ type Ref struct { // Export is one persisted Beleg export attempt. type Export struct { - ID string - Ref Ref - BelegID string - Status Status - Attempt int - LastError string - RetryAt *time.Time - ERPRef string - CreatedAt time.Time - UpdatedAt time.Time + ID string + Ref Ref + BelegID string + Status Status + Attempt int + LastError string + RetryAt *time.Time + ERPRef string + CreatedAt time.Time + UpdatedAt time.Time } var ErrNotFound = errors.New("erpexport: export nicht gefunden") diff --git a/connect/internal/httpclient/httpclient.go b/connect/internal/httpclient/httpclient.go deleted file mode 100644 index 0fa05f4e..00000000 --- a/connect/internal/httpclient/httpclient.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package httpclient liefert den HTTP-Client fuer ausgehende Dienstaufrufe -// (Muster aus archive/internal/httpclient, QA-15 F-11): Zeitlimit statt -// endlosem Warten, keine Weiterleitungen (ein Service-Token wuerde sonst an -// das Weiterleitungsziel mitgesendet). -package httpclient - -import ( - "net/http" - "time" -) - -const DefaultTimeout = 15 * time.Second - -var shared = New(DefaultTimeout) - -func Default() *http.Client { return shared } - -func New(timeout time.Duration) *http.Client { - return &http.Client{ - Timeout: timeout, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } -} diff --git a/connect/internal/httpclient/httpclient_test.go b/connect/internal/httpclient/httpclient_test.go deleted file mode 100644 index 0b620655..00000000 --- a/connect/internal/httpclient/httpclient_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package httpclient - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestDefaultHasTimeout(t *testing.T) { - if Default().Timeout != DefaultTimeout || DefaultTimeout <= 0 { - t.Fatalf("timeout = %s", Default().Timeout) - } -} - -func TestClientGivesUpOnHangingServer(t *testing.T) { - release := make(chan struct{}) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-release })) - defer srv.Close() - defer close(release) - - start := time.Now() - resp, err := New(200 * time.Millisecond).Get(srv.URL) - if err == nil { - _ = resp.Body.Close() - t.Fatal("kein fehler bei haengendem server") - } - if elapsed := time.Since(start); elapsed > 5*time.Second { - t.Fatalf("aufruf dauerte %s", elapsed) - } -} - -func TestClientDoesNotFollowRedirects(t *testing.T) { - target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Error("weiterleitungsziel wurde aufgerufen (Service-Token-Leck)") - })) - defer target.Close() - redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect) - })) - defer redirector.Close() - - req, _ := http.NewRequest(http.MethodPost, redirector.URL, strings.NewReader("x")) - req.Header.Set("X-Service-Token", "geheim") - resp, err := Default().Do(req) - if err != nil { - t.Fatalf("do: %v", err) - } - defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != http.StatusTemporaryRedirect { - t.Fatalf("status = %d, erwartet 307", resp.StatusCode) - } -} diff --git a/connect/internal/m365connector/m365connector.go b/connect/internal/m365connector/m365connector.go index 5755fb2a..0d75c1aa 100644 --- a/connect/internal/m365connector/m365connector.go +++ b/connect/internal/m365connector/m365connector.go @@ -18,8 +18,8 @@ import ( "strings" "time" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/policyclient/policyclient.go b/connect/internal/policyclient/policyclient.go deleted file mode 100644 index 16c67f95..00000000 --- a/connect/internal/policyclient/policyclient.go +++ /dev/null @@ -1,52 +0,0 @@ -// Package policyclient fragt Core (RBAC-06, POST /authorize) nach der -// Rechteentscheidung; Fehler und Ausfall fuehren immer zur Ablehnung. -package policyclient - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - "time" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTP *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: strings.TrimRight(baseURL, "/"), ServiceToken: serviceToken, HTTP: &http.Client{Timeout: 5 * time.Second}} -} - -func (c *Client) Allowed(ctx context.Context, tenant, role, permission string) (bool, error) { - body := map[string]string{"role": role, "permission": permission} - if tenant != "" { - body["tenant"] = tenant - } - payload, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/authorize", bytes.NewReader(payload)) - if err != nil { - return false, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Service-Token", c.ServiceToken) - resp, err := c.HTTP.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("policy: status %d", resp.StatusCode) - } - var out struct { - Allowed *bool `json:"allowed"` - } - if err := json.NewDecoder(http.MaxBytesReader(nil, resp.Body, 1<<16)).Decode(&out); err != nil || out.Allowed == nil { - return false, fmt.Errorf("policy: antwort ohne entscheidung") - } - return *out.Allowed, nil -} diff --git a/connect/internal/policyclient/policyclient_test.go b/connect/internal/policyclient/policyclient_test.go deleted file mode 100644 index 3ac23bed..00000000 --- a/connect/internal/policyclient/policyclient_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package policyclient - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" -) - -func TestAllowed_TenantScopedRequest_TrueOnAllow(t *testing.T) { - var gotBody map[string]string - var gotToken string - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gotToken = r.Header.Get("X-Service-Token") - if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { - t.Fatalf("body dekodieren: %v", err) - } - json.NewEncoder(w).Encode(map[string]bool{"allowed": true}) - })) - defer srv.Close() - - c := New(srv.URL, "svc-token-123") - allowed, err := c.Allowed(context.Background(), "acme", "editor", "connector.write") - if err != nil { - t.Fatalf("Allowed: %v", err) - } - if !allowed { - t.Fatal("erwartet allowed=true") - } - if gotToken != "svc-token-123" { - t.Fatalf("Service-Token nicht gesendet, erhalten: %q", gotToken) - } - if gotBody["tenant"] != "acme" { - t.Fatalf("Tenant fehlt/falsch in Anfrage: %q", gotBody["tenant"]) - } -} - -func TestAllowed_DeniedResponse_False(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - json.NewEncoder(w).Encode(map[string]bool{"allowed": false}) - })) - defer srv.Close() - - c := New(srv.URL, "svc-token-123") - allowed, err := c.Allowed(context.Background(), "acme", "viewer", "connector.write") - if err != nil { - t.Fatalf("Allowed: %v", err) - } - if allowed { - t.Fatal("erwartet allowed=false") - } -} - -func TestAllowed_ServiceFailure_DeniesInsteadOfSilentAllow(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - })) - defer srv.Close() - - c := New(srv.URL, "svc-token-123") - allowed, err := c.Allowed(context.Background(), "acme", "editor", "connector.write") - if err == nil { - t.Fatal("erwarteter Fehler bei Status 500 blieb aus") - } - if allowed { - t.Fatal("Ausfall darf nie zu allowed=true fuehren") - } -} - -func TestAllowed_TransportFailure_DeniesInsteadOfSilentAllow(t *testing.T) { - c := New("http://127.0.0.1:1", "svc-token-123") - allowed, err := c.Allowed(context.Background(), "acme", "editor", "connector.write") - if err == nil { - t.Fatal("erwarteter Transport-Fehler blieb aus") - } - if allowed { - t.Fatal("Ausfall darf nie zu allowed=true fuehren") - } -} diff --git a/connect/internal/statusapi/statusapi.go b/connect/internal/statusapi/statusapi.go index 7455d293..7348f108 100644 --- a/connect/internal/statusapi/statusapi.go +++ b/connect/internal/statusapi/statusapi.go @@ -11,7 +11,7 @@ import ( "net/http" "time" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/policyclient" + "gitea.perlbach24.de/scripte/nexarch/common/policyclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/syncjob" ) diff --git a/connect/internal/statusapi/statusapi_policy_test.go b/connect/internal/statusapi/statusapi_policy_test.go index f6a65802..adad71f9 100644 --- a/connect/internal/statusapi/statusapi_policy_test.go +++ b/connect/internal/statusapi/statusapi_policy_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/policyclient" + "gitea.perlbach24.de/scripte/nexarch/common/policyclient" ) // newFakePolicyAPI steht fuer Core's echten POST /authorize-Vertrag (nimmt diff --git a/connect/internal/syncjob/qa02_fault_injection_test.go b/connect/internal/syncjob/qa02_fault_injection_test.go index ce6064b2..c7af840b 100644 --- a/connect/internal/syncjob/qa02_fault_injection_test.go +++ b/connect/internal/syncjob/qa02_fault_injection_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" "gitea.perlbach24.de/scripte/nexarch/connect/internal/erpconnector" diff --git a/connect/internal/syncjob/syncjob_test.go b/connect/internal/syncjob/syncjob_test.go index e81712ee..78d80186 100644 --- a/connect/internal/syncjob/syncjob_test.go +++ b/connect/internal/syncjob/syncjob_test.go @@ -13,7 +13,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/connect/internal/migrate" "gitea.perlbach24.de/scripte/nexarch/connect/internal/testdb" diff --git a/connect/internal/testcorpus/testcorpus.go b/connect/internal/testcorpus/testcorpus.go index 14b39c32..fe7d0047 100644 --- a/connect/internal/testcorpus/testcorpus.go +++ b/connect/internal/testcorpus/testcorpus.go @@ -85,11 +85,11 @@ func writeCRM(dir string) error { contact := map[string]any{ "id": "51023", "properties": map[string]any{ - "firstname": "Anna", - "lastname": "Testperson", - "email": "anna.testperson@musterhandel-nordlicht.example", - "company": "Musterhandel Nordlicht GmbH", - "phone": "+49 431 5550100", + "firstname": "Anna", + "lastname": "Testperson", + "email": "anna.testperson@musterhandel-nordlicht.example", + "company": "Musterhandel Nordlicht GmbH", + "phone": "+49 431 5550100", "lifecyclestage": "customer", }, "createdAt": "2025-11-02T09:14:00Z", diff --git a/connect/internal/webhookclient/webhookclient.go b/connect/internal/webhookclient/webhookclient.go index 2762e056..0b1fe7b8 100644 --- a/connect/internal/webhookclient/webhookclient.go +++ b/connect/internal/webhookclient/webhookclient.go @@ -15,7 +15,7 @@ import ( "net/url" "time" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) type Client struct { diff --git a/connect/scripts/check-security-test-coverage.sh b/connect/scripts/check-security-test-coverage.sh index 41abb252..cb94a78f 100755 --- a/connect/scripts/check-security-test-coverage.sh +++ b/connect/scripts/check-security-test-coverage.sh @@ -10,9 +10,7 @@ set -euo pipefail SECURITY_PACKAGES=( "internal/crypto" "internal/credential" - "internal/auditclient" "internal/audittrail" - "internal/policyclient" "internal/archiveclient" "internal/datevexport" "internal/datevtransfer"