Test Coverage Report
Comprehensive documentation of all test files in the EnrichNode (DBPOC) codebase. Last updated: 2026-04-28
Summary
| Category | Files | Total Lines | Coverage Focus |
|---|---|---|---|
| API Tests | 12 | ~3,883 | REST endpoints, auth, CRUD, security |
| Enrichment Tests | 4 | ~1,034 | Pipeline, extraction quality, processors |
| Fetcher Tests | 2 | ~273 | External data sources (SCB, Bolagsverket) |
| Integration / E2E | 2 | ~672 | End-to-end flows, performance |
| Total | 20 | ~5,862 |
API Tests (tests/api/)
1. tests/api/auth.test.ts — Authentication API
| Attribute | Value |
|---|---|
| Lines | 317 |
| What it tests | Registration, login, JWT token validation, and rate limiting on auth endpoints |
| Dependencies | pg (Pool), live API server on localhost:3000 |
| Test DB cleanup | Deletes users/orgs matching %@authtest.com / AuthTest% |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
Registration | 7 | Register new user + org (201), duplicate email (409), invalid email (400), short password (400), missing email/password/org name (400 each) |
Login | 5 | Valid credentials (200), wrong password (401), non-existent user (401), missing email/password (400 each) |
Token Validation | 4 | Valid token accepted (200), invalid token (401), malformed auth header (401), missing token (401) |
Rate Limiting | 1 | Auth endpoints return X-RateLimit-* headers |
2. tests/api/companies.test.ts — Companies CRUD
| Attribute | Value |
|---|---|
| Lines | 472 |
| What it tests | Full Companies CRUD lifecycle, pagination, filtering, and RoPA audit logging |
| Dependencies | pg (Pool), live API server, authApiRequest() helper |
| Test DB cleanup | Deletes companies with source = 'companies_test' |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
Authentication | 2 | Unauthenticated requests rejected (401), invalid token rejected (401) |
List Companies | 5 | Pagination defaults (limit=20), custom limit, max limit capped at 100, offset, filter by source |
Create Company | 6 | Full fields (201), missing orgNr (400), missing name (400), invalid orgNr format (400), duplicate orgNr (409), minimal fields (201) |
Get Company | 3 | Get by orgNr (200), non-existent (404), special chars handled |
Update Company | 5 | Update name, address, SNI codes, multi-field update (all 200), empty update (400), non-existent (404) |
Delete Company | 2 | Delete existing (200 + verify 404), non-existent (404) |
RoPA Logging | 2 | INSERT and UPDATE operations logged to RoPA_Log |
3. tests/api/documents.test.ts — Documents CRUD + Vector Search
| Attribute | Value |
|---|---|
| Lines | 468 |
| What it tests | Document CRUD, pgvector embedding storage, and similarity search |
| Dependencies | pg (Pool), live API server, creates test project for document association |
| Test DB cleanup | Deletes test documents by ID |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
List Documents | 3 | List with pagination, custom limit/offset, filter by project_id |
Create Document | 7 | Full document (201), with 1536-dim embedding (201), missing name (400), empty name (400), missing project_id (400), non-existent project (404), invalid embedding dimensions (400) |
Get Document | 2 | Get by ID (200), non-existent (404) |
Update Document | 5 | Update name, content, embedding (all 200), empty name (400), non-existent (404) |
Delete Document | 2 | Delete existing (200 + verify 404), non-existent (404) |
Find Similar Documents | 2 | Similarity search returns ranked results (200), non-existent doc (404) |
RoPA Logging | 1 | Document creation logged to RoPA_Log |
4. tests/api/index.test.ts — End-to-End API Integration
| Attribute | Value |
|---|---|
| Lines | 655 |
| What it tests | Complete API integration across all endpoints in a single test run |
| Dependencies | pg (Pool), live API server, apiRequest() + authApiRequest() helpers |
| Test DB cleanup | Comprehensive cleanup of all test data by pattern |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
Health Check | 1 | Returns healthy status with DB connected |
Authentication | 6 | Register (201), duplicate email (409), invalid email (400), short password (400), login (200), invalid credentials (401) |
Organizations API | 5 | List (200), get by ID (200), non-existent (404), invalid ID (400), update (200) |
Users API | 5 | List (200), get by ID (200), create (201), update email (200), update password (200) |
Projects API | 5 | Create (201), list (200), get by ID (200), update (200), filter by org (200) |
Documents API | 7 | Create (201), with embedding (201), invalid dimensions (400), list (200), filter by project (200), get by ID (200), update (200) |
Companies API | 5 | Auth required (401), list (200), create (201), get by orgNr (200), duplicate (409) |
Leads API | 4 | List (200), create (201), get by org_nr (200), validate lead (200) |
Search API | 2 | Basic search (200), advanced search with filters (200) |
Rate Limiting | 2 | Rate limit headers present, stricter limits on auth endpoints |
Cleanup | 4 | Delete document, project, user, organization (all 200) |
5. tests/api/kundkort-enrich.test.ts — Kundkort Enrich Endpoint
| Attribute | Value |
|---|---|
| Lines | 88 |
| What it tests | POST /api/kundkort/:orgNr/enrich endpoint for on-demand company enrichment |
| Dependencies | src/db/index.js (pool), live API server |
| Test orgNr | 5565672655 (Gordons Project AB) |
Key Test Cases:
| Test | Assertion |
|---|---|
| Unauthenticated request | Returns 401 (or 200 in KEYCLOAK_DEV_MODE) |
| Enrich existing company | Returns 200 with validationResult containing isHeltValiderad, leadScore, validatedAt |
| Non-existent company | Returns 404 |
| Invalid orgNr format | Returns 400 |
6. tests/api/leads.test.ts — Leads CRUD + Validation
| Attribute | Value |
|---|---|
| Lines | 536 |
| What it tests | Full Leads CRUD, advanced filtering, and 4-layer validation endpoint |
| Dependencies | pg (Pool), live API server, authApiRequest() helper |
| Test DB cleanup | Deletes leads with source = 'leads_test' |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
List Leads | 7 | Pagination, filter by is_active, is_validated, sni_code, city, min_confidence, combined filters |
Create Lead | 6 | Full fields (201), minimal fields with defaults (201), missing org_nr (400), missing name (400), invalid org_nr (400), duplicate org_nr (409) |
Get Lead | 2 | Get by org_nr (200), non-existent (404) |
Update Lead | 5 | Update name, status, validation fields (all 200), empty update (400), non-existent (404) |
Validate Lead | 4 | Full validation result (200), status-only (200), non-existent (404), invalid result format (400) |
RoPA Logging | 2 | Lead creation and validation logged to RoPA_Log |
7. tests/api/organizations.test.ts — Organizations CRUD
| Attribute | Value |
|---|---|
| Lines | 308 |
| What it tests | Organization CRUD with UUID validation and referential integrity |
| Dependencies | pg (Pool), live API server, authApiRequest() helper |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
List Organizations | 2 | List with pagination, custom limit/offset |
Get Organization | 4 | Get by ID (200), non-existent (404), invalid ID format (400), malformed UUID (400) |
Create Organization | 4 | Create (201), missing name (400), empty name (400), name too long (400) |
Update Organization | 5 | Update name (200), empty name (400), name too long (400), non-existent (404), invalid ID (400) |
Delete Organization | 4 | Delete (200 + verify 404), non-existent (404), invalid ID (400), org with users (409) |
RoPA Logging | 1 | Organization creation logged to RoPA_Log |
8. tests/api/projects.test.ts — Projects CRUD
| Attribute | Value |
|---|---|
| Lines | 322 |
| What it tests | Project CRUD with organization filtering |
| Dependencies | pg (Pool), live API server, authApiRequest() helper |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
List Projects | 3 | List with pagination, custom limit/offset, filter by organization_id |
Get Project | 2 | Get by ID (200), non-existent (404) |
Create Project | 4 | Create (201), missing name (400), empty name (400), missing organization_id (400), non-existent org (404) |
Update Project | 3 | Update name (200), empty name (400), non-existent (404) |
Delete Project | 2 | Delete (200 + verify 404), non-existent (404) |
RoPA Logging | 1 | Project creation logged to RoPA_Log |
9. tests/api/search.test.ts — Search Endpoints
| Attribute | Value |
|---|---|
| Lines | 332 |
| What it tests | Basic text search and advanced multi-filter search across companies and leads |
| Dependencies | pg (Pool), live API server, seeds test data into companies and leads tables |
| Test DB cleanup | Deletes rows with source = 'search_test' |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
Basic Search | 7 | Search by query (200), missing query (400), empty query (400), limit/offset respected, results sorted by confidence, multi-table search, breakdown counts |
Advanced Search | 10 | Filter by name, city, SNI code, org number, postal code, isActive, isValidated, minConfidence, combined filters, full-text + filter combo |
Search Results | 3 | Consistent result structure (type, id, org_nr, name), special characters handled, Unicode (e.g. Göteborg) handled |
10. tests/api/security.test.ts — Security Hardening
| Attribute | Value |
|---|---|
| Lines | 134 |
| What it tests | JWT forgery prevention, production env var requirements, CSV injection sanitization, password hashing consistency |
| Dependencies | src/api/auth.js (verifyTokenSignature), src/api/export.js (sanitizeCSVValue), src/api/middleware/rate-limit.js |
| Mocks | None — tests pure functions and module loading |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
JWT Token Forgery Prevention | 4 | Invalid signature rejected, malformed tokens rejected, tampered payload rejected, expired tokens rejected |
Environment Variables | 3 | JWT_SECRET, KEYCLOAK_CLIENT_SECRET, HASH_SALT required in production (≥32 chars for secrets) |
CSV Injection Protection | 2 | Formula-starting chars (=, +, -, @) sanitized with leading ', normal data preserved |
Password Hashing | 1 | Auth and users modules load without errors (documents bcrypt cost 12) |
Rate Limiting Headers | 1 | Rate limit module is defined and loadable |
11. tests/api/structure.test.ts — Handler Export Validation
| Attribute | Value |
|---|---|
| Lines | 85 |
| What it tests | Static verification that API handler modules export all expected methods |
| Dependencies | src/api/companies.js, src/api/leads.js, src/api/search.js, src/api/validation.js |
| Mocks | None — pure module import tests |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
Company Handlers | 1 | Exports list, get, create, update, remove |
Lead Handlers | 1 | Exports list, get, create, update, validate |
Search Handlers | 1 | Exports search, advanced |
Validation | 5 | validateCompany accepts valid input, rejects invalid; validateLead accepts/rejects; validateValidationResult accepts valid result |
12. tests/api/users.test.ts — Users CRUD
| Attribute | Value |
|---|---|
| Lines | 343 |
| What it tests | User CRUD with email and password validation |
| Dependencies | pg (Pool), live API server, authApiRequest() helper |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
List Users | 2 | List with pagination, custom limit/offset |
Get User | 3 | Get by ID (200), non-existent (404), invalid ID format (400) |
Create User | 7 | Create (201), missing email (400), missing password (400), invalid email (400), short password (400), duplicate email (409), non-existent org (404) |
Update User | 5 | Update email (200), update password (200), invalid email (400), short password (400), non-existent (404) |
Delete User | 2 | Delete (200 + verify 404), non-existent (404) |
Enrichment Tests (tests/enrichment/)
13. tests/enrichment/board-members-integration.test.ts — BV Board Members Pipeline
| Attribute | Value |
|---|---|
| Lines | 31 |
| What it tests | Integration of Bolagsverket board member data into the enrichment pipeline (enrichV7) |
| Dependencies | src/enrichment/pipeline.js (enrichV7) |
| Timeout | 30,000 ms per test |
| Mocks | None — calls real enrichment pipeline with bypass_cache: true |
Key Test Cases:
| Test | Assertion |
|---|---|
BV board members populate updated_fields | Result has enrichment_status, contacts array, updated_fields array |
| Pipeline integration | Result has contacts, company with matching org_nr |
Test Data: org_nr: 5565672655 (Gordons Project AB)
14. tests/enrichment/crawlee-quality.test.ts — Crawlee Extraction Quality
| Attribute | Value |
|---|---|
| Lines | 308 |
| What it tests | Contact extraction accuracy from text and image alt attributes, name normalization, role inference, and false-positive guards |
| Dependencies | src/enrichment/sources/crawlee.js, src/enrichment/processors/nameUtils.js |
| Mocks | None — uses inline HTML/text fixtures only |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
extractContactsFromText — vendfox-style | 6 | Extracts 3 contacts, name correct, VD role found, email found, phone with type, no email bleed-over between contacts, all names pass isValidPersonName |
extractContactsFromText — rejects non-person | 3 | Rejects navigation phrases, single-word entries, company names with AB |
extractContactsFromText — landline type | 2 | Detects landline (08-xxx), mobile not misclassified |
extractContactsFromImgAlts — team images | 5 | Extracts 3 from team imgs, skips logo/banner, role from alt text, strips company name from role, all names valid |
extractContactsFromImgAlts — name particles | 1 | Handles Dutch/Swedish particles (van, de, af) |
extractContactsFromImgAlts — single-quoted attrs | 1 | Handles single-quoted src/alt |
normalizeName | 3 | Strips accents (André → andre), André === Andre, full names |
maybeFlipSwedishName | 4 | Flips “Last First” → “First Last”, preserves normal order, no-op on 3-word names |
inferRoleType — PR/communications | 7 | PR-konsult, Kommunikationskonsult, Copywriter, Digital strateg → Marknadschef; Rekryterare, Talent Manager → Personalansvarig |
inferRoleType — existing roles | 7 | VD, CEO→VD, CFO, ekonomichef→CFO, CTO, Grundare, unknown→Övrig |
extractContactsFromImgAlts — all-caps roles | 3 | VD/CEO parsed as role not name, normal capitalized last name kept |
extractContactsFromImgAlts — path separators | 2 | Hyphen and underscore in image paths matched |
extractContactsFromImgAlts — filename false-positives | 2 | Rejects underscore single-word alts, digit-containing alts |
15. tests/enrichment/firecrawl.test.ts — Firecrawl Extractor
| Attribute | Value |
|---|---|
| Lines | 512 |
| What it tests | Firecrawl LLM-structured extraction pipeline: contact page discovery, mock-client full extraction, multi-page merge, HTML fallback, and error handling |
| Dependencies | src/enrichment/sources/firecrawl.js |
| Mocks | Injected mock Firecrawl client via _setClientForTest() |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
discoverContactPages — preloaded URLs | 4 | Accepts /kontakt, /contact, /om-oss, /team, /medarbetare, /ledning, /styrelse; excludes non-contact; empty when none found; caps at 3 |
CONTACT_PAGE_PATTERNS | 2 | Matches expected paths, rejects unrelated |
discoverContactPages — depth filter | 3 | Accepts depth 1, accepts depth 2 for multilingual, rejects depth > 2 |
scrapeWebsiteFirecrawl — https:// guard | 1 | Strips protocol prefix to avoid https://https:// |
missing API key | 2 | Throws informative error when no FIRECRAWL_API_KEY |
scrapeWebsiteFirecrawl with mock client | 14 | Output shape matches scrapeWebsite() interface, isValidPersonName filter, role inference, name deduplication, email collection + lowercasing, service dedup + limit 8, social links, method: firecrawl, method: failed on null, source tagged, company phone collected, tech_stack: [] |
multi-page merge | 2 | Merges contacts from 2 pages, uses headline from first successful page only |
guessContactUrls | 4 | Returns 200-responding URLs, falls back to homepage on 404, caps at 3, handles fetch errors |
extractContactsFromHtml — cheerio fallback | 7 | Finds name from h3, email from mailto, skips seen names, rejects non-person headings, empty for no headings, source tagged html-fallback, confidence low |
scrapeWebsiteFirecrawl — html fallback | 2 | No crash when HTML undefined, uses HTML fallback when Firecrawl returns 0 contacts |
16. tests/enrichment/processors.test.ts — Processor Utilities
| Attribute | Value |
|---|---|
| Lines | 183 |
| What it tests | Pure function tests for name utilities, email utilities, phone extraction, and lead scoring |
| Dependencies | src/enrichment/processors/nameUtils.js, emailUtils.js, phoneUtils.js, scoring.js |
| Mocks | None — all pure functions |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
inferRoleType | 8 | VD variants, CFO variants, default Övrig, PR→Marknadschef, compound split, Copywriter→Marknadschef, Rekryterare→Personalansvarig |
normalizeName | 2 | Strips accents, André === Andre |
maybeFlipSwedishName | 3 | Flips “Last First”, preserves normal order, no-op on 3-word |
normalizeCompanyName | 2 | Strips AB/HB suffix |
extractBrandName | 3 | Strips generic words + AB, undefined when no generic word, handles multiple generics |
isValidPersonName | 4 | Accepts valid Swedish names, rejects single word, rejects AB, rejects email-like, rejects nav words |
normSe | 1 | Normalizes Swedish chars (Å, Ä, Ö) |
extractEmails | 2 | Extracts emails from text, deduplicates |
generateEmailGuesses | 3 | Low confidence + generated_guess source, firstname.lastname@domain first, empty for single-word names |
detectEmailPattern | 2 | Detects firstname.lastname pattern, detects initial pattern |
extractPhones | 2 | Extracts Swedish mobile numbers with type, empty for no phones |
calculateLeadScore | 3 | Returns 0 for empty, awards points for VD contact, caps at 10 |
Fetcher Tests (tests/fetchers/)
17. tests/fetchers/bolagsverket.test.ts — Bolagsverket Fetcher
| Attribute | Value |
|---|---|
| Lines | 113 |
| What it tests | ZIP download orchestration: downloadAndExtract() coordinates download → extract → cleanup |
| Dependencies | src/fetchers/bolagsverket/index.js, download.js, extract.js |
| Mocks | axios (download streaming), download.js, extract.js |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
downloadFile | 1 | Placeholder — notes Bun mock.module limitations for testing actual vs mocked |
downloadAndExtract | 2 | Orchestrates download + extract + cleanup, verifies ZIP deleted after extraction; uses BOLAGSVERKET_API_URL env var as default URL |
Mock Behavior:
downloadFile: Writes mock ZIP content to destinationextractZip: Returns['/tmp/extracted/file1.ixbrl', '/tmp/extracted/file2.ixbrl']axios: Simulates streaming response withpipe+endevents
18. tests/fetchers/scb.test.ts — SCB API Client
| Attribute | Value |
|---|---|
| Lines | 160 |
| What it tests | SCB PxWebApi v2 integration: table list, metadata, data fetch, and company synthesis |
| Dependencies | src/fetchers/scb/index.js, src/fetchers/scb/types.js |
| Mocks | axios module (Bun mock.module) with fixture responses |
Key Test Cases:
| Describe Block | Tests | Assertions |
|---|---|---|
fetchTableList | 1 | Returns table list with pagination, correct table ID and label |
fetchTableMetadata | 1 | Returns metadata with variables, correct SNI2007 variable |
fetchTableData | 1 | Returns JSON-stat2 response with values and dimensions |
fetchCompanies | 3 | Returns array of companies, every company has required shape (orgNr, name, address, sni), all orgNrs prefixed with SCB- |
Fixture Data:
MOCK_TABLE_LIST: 1 table (NV/NV0109/NV0109A/NV0109A01— Enterprises by industry)MOCK_METADATA: 2 variables (SNI2007, ContentsCode)MOCK_DATA: JSON-stat2 with values[1234, 5678]
Integration / End-to-End Tests
19. tests/domainDiscovery.test.ts — Domain Discovery Pipeline
| Attribute | Value |
|---|---|
| Lines | 17 |
| What it tests | Domain discovery guards against known bad domains (hosting providers, municipalities) |
| Dependencies | src/enrichment/sources/domain.js (findCompanyDomain) |
| Mocks | None — live domain discovery with real company names |
Key Test Cases:
| Test | Assertion |
|---|---|
| Reject hosting provider | findCompanyDomain('Pontán Uppsala AB', '5569810061', 'Örsundsbro') does NOT return 180.se |
| Reject municipality | findCompanyDomain('TMP I Uppsala AB', '5565478202', 'Uppsala') does NOT return uppsala.se |
20. tests/speed.test.ts — Performance Benchmarks
| Attribute | Value |
|---|---|
| Lines | 22 |
| What it tests | Registry lookup latency and correctness |
| Dependencies | src/enrichment/sources/registry.js (getDomainFromRegistry), src/enrichment/sources/domain.js (findCompanyDomain) |
| Mocks | None — tests against seeded database |
Key Test Cases:
| Test | Assertion |
|---|---|
| Registry lookup for known entity | Returns ['pontan.se'] if seeded, or empty array if not |
| Registry lookup latency | Completes in under 50ms |
Test Infrastructure Patterns
Common Patterns Across API Tests
- Auth Setup: Most API tests register a test user + org in
beforeAll, falling back to login if already exists authApiRequest()Helper: Wrapsfetch()withAuthorization: Bearer <token>andContent-Type: application/json- DB Cleanup:
beforeEach/afterAlldeletes test rows bysourcecolumn or email pattern - RoPA Verification: Direct PostgreSQL queries against
RoPA_Logtable verify audit compliance - Pagination Assertions: All list endpoints return
{ data: [], pagination: { total, limit, offset, hasMore } }
Mocking Strategy
| Layer | Approach | Examples |
|---|---|---|
| HTTP Client | bun:test mock.module('axios', ...) | scb.test.ts, bolagsverket.test.ts |
| Internal Modules | mock.module('../../src/...', ...) | bolagsverket.test.ts (download/extract) |
| External API Client | Dependency injection (_setClientForTest) | firecrawl.test.ts |
| Live Server | Integration tests against localhost:3000 | All tests/api/*.test.ts |
| Pure Functions | No mocks — direct function calls | processors.test.ts, crawlee-quality.test.ts |
Environment Variables Used
| Variable | Default | Used In |
|---|---|---|
PGHOST | localhost | All API + DB tests |
PGPORT | 5433 | All API + DB tests |
PGUSER | user | All API + DB tests |
PGPASSWORD | password | All API + DB tests |
PGDATABASE | enrichnodedb | All API + DB tests |
API_PORT | 3000 | All API tests |
FIRECRAWL_API_KEY | — | firecrawl.test.ts (mocked) |
JWT_SECRET | — | security.test.ts |
KEYCLOAK_DEV_MODE | — | kundkort-enrich.test.ts |
BOLAGSVERKET_API_URL | — | bolagsverket.test.ts |
NODE_ENV | — | security.test.ts |
Coverage Gaps & Recommendations
| Gap | Suggested Test |
|---|---|
No dedicated src/api/export.test.ts | CSV/JSON export with GDPR filtering, large dataset handling |
No dedicated src/api/scrape.test.ts | On-demand scraping endpoint |
No src/workers/*.test.ts | BullMQ worker job processing, error handling, retry logic |
No src/compliance.test.ts in tests/ | hash_contact(), logRoPA() unit tests |
No src/cache.test.ts | Redis cache get/set, TTL behavior |
No src/lib/smtpEmailValidator.test.ts | SMTP validation logic (939-line module) |
| No frontend tests | Kundkort React components |
No tests/api/export.test.ts | Export endpoint security (CSV injection already covered in security.test.ts) |