//go:build integration // Enterprise connector (iCIMS) integration. Proves at runtime against a REAL Postgres and a fake iCIMS server // what the unit tests cannot: connector.Sync ingests a page of candidates as STRUCTURED scoring inputs (skills // as rows) plus a sourcing_log provenance row each, advances the connector_sync_state watermark, and is // idempotent (a re run dedupes rather than duplicating), all through the proven sourcing path with clearance // left unknown. Migration 0038 (connector_sync_state) must be applied. // // Run: go test -tags=integration ./internal/integration/ -run ConnectorICIMS -v // Env: MM_MIGRATOR_DSN. package integration import ( "context" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/missionworkforce/missionmatch/internal/platform/connector" "github.com/missionworkforce/missionmatch/internal/platform/extraction" ) // fakeICIMSServer serves a bearer token and one page of two candidates (ignoring updatedSince, so a re run sees // the same records and must dedupe). func fakeICIMSServer() *httptest.Server { mux := http.NewServeMux() mux.HandleFunc("/oauth/token", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"access_token":"tok","expires_in":3600}`)) }) mux.HandleFunc("/customers/CUST1/search/people", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"people":[ {"id":"ic-1","name":"Connector Alpha","title":"RMF Engineer","location":"Honolulu HI","skills":["rmf","cloud security"],"years":7,"updated":"2026-02-01T00:00:00Z"}, {"id":"ic-2","name":"Connector Beta","title":"ISSO","location":"Santa Fe NM","skills":["ato"],"years":9,"updated":"2026-03-10T00:00:00Z"} ],"nextPage":""}`)) }) return httptest.NewServer(mux) } func TestConnectorICIMSSyncIngestsAndAdvancesWatermark(t *testing.T) { ctx := context.Background() org := seedOrg(t) srv := fakeICIMSServer() defer srv.Close() c := connector.NewICIMS("CUST1", "cid", "secret", srv.URL, srv.Client()) ext := extraction.NewDeterministic() res, err := connector.Sync(ctx, migrator, org, c, ext, "test legal basis", 1000) if err != nil { t.Fatalf("sync: %v", err) } if res.Synced != 2 { t.Fatalf("the first sync must ingest 2 candidates, got %d", res.Synced) } // Two external candidates exist for the org, tagged with the iCIMS external ids. if n := countExternal(t, org); n != 2 { t.Fatalf("2 external candidates must be created, got %d", n) } // Their structured skills were attached (a connector record is a real scoring input, not a headline). var skillCount int if err := migrator.QueryRow(ctx, `SELECT count(*) FROM candidate_skills cs JOIN candidates c ON c.id = cs.candidate_id WHERE c.organization_id=$1 AND c.external_id IN ('ic-1','ic-2')`, org).Scan(&skillCount); err != nil { t.Fatalf("count skills: %v", err) } if skillCount < 3 { t.Fatalf("the structured skills must be attached (rmf, cloud security, ato), got %d", skillCount) } // A sourcing_log provenance row exists per record. var logCount int if err := migrator.QueryRow(ctx, `SELECT count(*) FROM sourcing_log WHERE organization_id=$1 AND external_id IN ('ic-1','ic-2') AND provider='icims'`, org).Scan(&logCount); err != nil { t.Fatalf("count sourcing_log: %v", err) } if logCount != 2 { t.Fatalf("a sourcing_log row must be written per record, got %d", logCount) } // The watermark advanced to the max updated timestamp seen (compared as an instant, timezone independent). var watermarkOK bool if err := migrator.QueryRow(ctx, `SELECT watermark = '2026-03-10T00:00:00Z'::timestamptz FROM connector_sync_state WHERE organization_id=$1 AND connector_name='icims'`, org).Scan(&watermarkOK); err != nil { t.Fatalf("read watermark: %v", err) } if !watermarkOK { t.Fatal("the watermark must advance to the max updated instant 2026-03-10T00:00:00Z") } // Idempotency: a second sync over the same records dedupes (enriches), it does not create duplicates. res2, err := connector.Sync(ctx, migrator, org, c, ext, "test legal basis", 1000) if err != nil { t.Fatalf("second sync: %v", err) } if res2.Synced != 2 { t.Fatalf("the second sync still processes 2 records, got %d", res2.Synced) } if n := countExternal(t, org); n != 2 { t.Fatalf("a re run must not duplicate candidates, still expect 2, got %d", n) } } // countExternal counts the org's external candidates carrying the iCIMS ids. func countExternal(t *testing.T, org uuid.UUID) int { t.Helper() var n int if err := migrator.QueryRow(context.Background(), `SELECT count(*) FROM candidates WHERE organization_id=$1 AND external_id IN ('ic-1','ic-2')`, org).Scan(&n); err != nil { t.Fatalf("count external candidates: %v", err) } return n }