Test Coverage Report

Comprehensive documentation of all test files in the EnrichNode (DBPOC) codebase. Last updated: 2026-04-28

Summary

CategoryFilesTotal LinesCoverage Focus
API Tests12~3,883REST endpoints, auth, CRUD, security
Enrichment Tests4~1,034Pipeline, extraction quality, processors
Fetcher Tests2~273External data sources (SCB, Bolagsverket)
Integration / E2E2~672End-to-end flows, performance
Total20~5,862

API Tests (tests/api/)

1. tests/api/auth.test.ts — Authentication API

AttributeValue
Lines317
What it testsRegistration, login, JWT token validation, and rate limiting on auth endpoints
Dependenciespg (Pool), live API server on localhost:3000
Test DB cleanupDeletes users/orgs matching %@authtest.com / AuthTest%

Key Test Cases:

Describe BlockTestsAssertions
Registration7Register new user + org (201), duplicate email (409), invalid email (400), short password (400), missing email/password/org name (400 each)
Login5Valid credentials (200), wrong password (401), non-existent user (401), missing email/password (400 each)
Token Validation4Valid token accepted (200), invalid token (401), malformed auth header (401), missing token (401)
Rate Limiting1Auth endpoints return X-RateLimit-* headers

2. tests/api/companies.test.ts — Companies CRUD

AttributeValue
Lines472
What it testsFull Companies CRUD lifecycle, pagination, filtering, and RoPA audit logging
Dependenciespg (Pool), live API server, authApiRequest() helper
Test DB cleanupDeletes companies with source = 'companies_test'

Key Test Cases:

Describe BlockTestsAssertions
Authentication2Unauthenticated requests rejected (401), invalid token rejected (401)
List Companies5Pagination defaults (limit=20), custom limit, max limit capped at 100, offset, filter by source
Create Company6Full fields (201), missing orgNr (400), missing name (400), invalid orgNr format (400), duplicate orgNr (409), minimal fields (201)
Get Company3Get by orgNr (200), non-existent (404), special chars handled
Update Company5Update name, address, SNI codes, multi-field update (all 200), empty update (400), non-existent (404)
Delete Company2Delete existing (200 + verify 404), non-existent (404)
RoPA Logging2INSERT and UPDATE operations logged to RoPA_Log

AttributeValue
Lines468
What it testsDocument CRUD, pgvector embedding storage, and similarity search
Dependenciespg (Pool), live API server, creates test project for document association
Test DB cleanupDeletes test documents by ID

Key Test Cases:

Describe BlockTestsAssertions
List Documents3List with pagination, custom limit/offset, filter by project_id
Create Document7Full 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 Document2Get by ID (200), non-existent (404)
Update Document5Update name, content, embedding (all 200), empty name (400), non-existent (404)
Delete Document2Delete existing (200 + verify 404), non-existent (404)
Find Similar Documents2Similarity search returns ranked results (200), non-existent doc (404)
RoPA Logging1Document creation logged to RoPA_Log

4. tests/api/index.test.ts — End-to-End API Integration

AttributeValue
Lines655
What it testsComplete API integration across all endpoints in a single test run
Dependenciespg (Pool), live API server, apiRequest() + authApiRequest() helpers
Test DB cleanupComprehensive cleanup of all test data by pattern

Key Test Cases:

Describe BlockTestsAssertions
Health Check1Returns healthy status with DB connected
Authentication6Register (201), duplicate email (409), invalid email (400), short password (400), login (200), invalid credentials (401)
Organizations API5List (200), get by ID (200), non-existent (404), invalid ID (400), update (200)
Users API5List (200), get by ID (200), create (201), update email (200), update password (200)
Projects API5Create (201), list (200), get by ID (200), update (200), filter by org (200)
Documents API7Create (201), with embedding (201), invalid dimensions (400), list (200), filter by project (200), get by ID (200), update (200)
Companies API5Auth required (401), list (200), create (201), get by orgNr (200), duplicate (409)
Leads API4List (200), create (201), get by org_nr (200), validate lead (200)
Search API2Basic search (200), advanced search with filters (200)
Rate Limiting2Rate limit headers present, stricter limits on auth endpoints
Cleanup4Delete document, project, user, organization (all 200)

5. tests/api/kundkort-enrich.test.ts — Kundkort Enrich Endpoint

AttributeValue
Lines88
What it testsPOST /api/kundkort/:orgNr/enrich endpoint for on-demand company enrichment
Dependenciessrc/db/index.js (pool), live API server
Test orgNr5565672655 (Gordons Project AB)

Key Test Cases:

TestAssertion
Unauthenticated requestReturns 401 (or 200 in KEYCLOAK_DEV_MODE)
Enrich existing companyReturns 200 with validationResult containing isHeltValiderad, leadScore, validatedAt
Non-existent companyReturns 404
Invalid orgNr formatReturns 400

6. tests/api/leads.test.ts — Leads CRUD + Validation

AttributeValue
Lines536
What it testsFull Leads CRUD, advanced filtering, and 4-layer validation endpoint
Dependenciespg (Pool), live API server, authApiRequest() helper
Test DB cleanupDeletes leads with source = 'leads_test'

Key Test Cases:

Describe BlockTestsAssertions
List Leads7Pagination, filter by is_active, is_validated, sni_code, city, min_confidence, combined filters
Create Lead6Full fields (201), minimal fields with defaults (201), missing org_nr (400), missing name (400), invalid org_nr (400), duplicate org_nr (409)
Get Lead2Get by org_nr (200), non-existent (404)
Update Lead5Update name, status, validation fields (all 200), empty update (400), non-existent (404)
Validate Lead4Full validation result (200), status-only (200), non-existent (404), invalid result format (400)
RoPA Logging2Lead creation and validation logged to RoPA_Log

7. tests/api/organizations.test.ts — Organizations CRUD

AttributeValue
Lines308
What it testsOrganization CRUD with UUID validation and referential integrity
Dependenciespg (Pool), live API server, authApiRequest() helper

Key Test Cases:

Describe BlockTestsAssertions
List Organizations2List with pagination, custom limit/offset
Get Organization4Get by ID (200), non-existent (404), invalid ID format (400), malformed UUID (400)
Create Organization4Create (201), missing name (400), empty name (400), name too long (400)
Update Organization5Update name (200), empty name (400), name too long (400), non-existent (404), invalid ID (400)
Delete Organization4Delete (200 + verify 404), non-existent (404), invalid ID (400), org with users (409)
RoPA Logging1Organization creation logged to RoPA_Log

8. tests/api/projects.test.ts — Projects CRUD

AttributeValue
Lines322
What it testsProject CRUD with organization filtering
Dependenciespg (Pool), live API server, authApiRequest() helper

Key Test Cases:

Describe BlockTestsAssertions
List Projects3List with pagination, custom limit/offset, filter by organization_id
Get Project2Get by ID (200), non-existent (404)
Create Project4Create (201), missing name (400), empty name (400), missing organization_id (400), non-existent org (404)
Update Project3Update name (200), empty name (400), non-existent (404)
Delete Project2Delete (200 + verify 404), non-existent (404)
RoPA Logging1Project creation logged to RoPA_Log

9. tests/api/search.test.ts — Search Endpoints

AttributeValue
Lines332
What it testsBasic text search and advanced multi-filter search across companies and leads
Dependenciespg (Pool), live API server, seeds test data into companies and leads tables
Test DB cleanupDeletes rows with source = 'search_test'

Key Test Cases:

Describe BlockTestsAssertions
Basic Search7Search by query (200), missing query (400), empty query (400), limit/offset respected, results sorted by confidence, multi-table search, breakdown counts
Advanced Search10Filter by name, city, SNI code, org number, postal code, isActive, isValidated, minConfidence, combined filters, full-text + filter combo
Search Results3Consistent result structure (type, id, org_nr, name), special characters handled, Unicode (e.g. Göteborg) handled

10. tests/api/security.test.ts — Security Hardening

AttributeValue
Lines134
What it testsJWT forgery prevention, production env var requirements, CSV injection sanitization, password hashing consistency
Dependenciessrc/api/auth.js (verifyTokenSignature), src/api/export.js (sanitizeCSVValue), src/api/middleware/rate-limit.js
MocksNone — tests pure functions and module loading

Key Test Cases:

Describe BlockTestsAssertions
JWT Token Forgery Prevention4Invalid signature rejected, malformed tokens rejected, tampered payload rejected, expired tokens rejected
Environment Variables3JWT_SECRET, KEYCLOAK_CLIENT_SECRET, HASH_SALT required in production (≥32 chars for secrets)
CSV Injection Protection2Formula-starting chars (=, +, -, @) sanitized with leading ', normal data preserved
Password Hashing1Auth and users modules load without errors (documents bcrypt cost 12)
Rate Limiting Headers1Rate limit module is defined and loadable

11. tests/api/structure.test.ts — Handler Export Validation

AttributeValue
Lines85
What it testsStatic verification that API handler modules export all expected methods
Dependenciessrc/api/companies.js, src/api/leads.js, src/api/search.js, src/api/validation.js
MocksNone — pure module import tests

Key Test Cases:

Describe BlockTestsAssertions
Company Handlers1Exports list, get, create, update, remove
Lead Handlers1Exports list, get, create, update, validate
Search Handlers1Exports search, advanced
Validation5validateCompany accepts valid input, rejects invalid; validateLead accepts/rejects; validateValidationResult accepts valid result

12. tests/api/users.test.ts — Users CRUD

AttributeValue
Lines343
What it testsUser CRUD with email and password validation
Dependenciespg (Pool), live API server, authApiRequest() helper

Key Test Cases:

Describe BlockTestsAssertions
List Users2List with pagination, custom limit/offset
Get User3Get by ID (200), non-existent (404), invalid ID format (400)
Create User7Create (201), missing email (400), missing password (400), invalid email (400), short password (400), duplicate email (409), non-existent org (404)
Update User5Update email (200), update password (200), invalid email (400), short password (400), non-existent (404)
Delete User2Delete (200 + verify 404), non-existent (404)

Enrichment Tests (tests/enrichment/)

13. tests/enrichment/board-members-integration.test.ts — BV Board Members Pipeline

AttributeValue
Lines31
What it testsIntegration of Bolagsverket board member data into the enrichment pipeline (enrichV7)
Dependenciessrc/enrichment/pipeline.js (enrichV7)
Timeout30,000 ms per test
MocksNone — calls real enrichment pipeline with bypass_cache: true

Key Test Cases:

TestAssertion
BV board members populate updated_fieldsResult has enrichment_status, contacts array, updated_fields array
Pipeline integrationResult 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

AttributeValue
Lines308
What it testsContact extraction accuracy from text and image alt attributes, name normalization, role inference, and false-positive guards
Dependenciessrc/enrichment/sources/crawlee.js, src/enrichment/processors/nameUtils.js
MocksNone — uses inline HTML/text fixtures only

Key Test Cases:

Describe BlockTestsAssertions
extractContactsFromText — vendfox-style6Extracts 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-person3Rejects navigation phrases, single-word entries, company names with AB
extractContactsFromText — landline type2Detects landline (08-xxx), mobile not misclassified
extractContactsFromImgAlts — team images5Extracts 3 from team imgs, skips logo/banner, role from alt text, strips company name from role, all names valid
extractContactsFromImgAlts — name particles1Handles Dutch/Swedish particles (van, de, af)
extractContactsFromImgAlts — single-quoted attrs1Handles single-quoted src/alt
normalizeName3Strips accents (André → andre), André === Andre, full names
maybeFlipSwedishName4Flips “Last First” → “First Last”, preserves normal order, no-op on 3-word names
inferRoleType — PR/communications7PR-konsult, Kommunikationskonsult, Copywriter, Digital strateg → Marknadschef; Rekryterare, Talent Manager → Personalansvarig
inferRoleType — existing roles7VD, CEO→VD, CFO, ekonomichef→CFO, CTO, Grundare, unknown→Övrig
extractContactsFromImgAlts — all-caps roles3VD/CEO parsed as role not name, normal capitalized last name kept
extractContactsFromImgAlts — path separators2Hyphen and underscore in image paths matched
extractContactsFromImgAlts — filename false-positives2Rejects underscore single-word alts, digit-containing alts

15. tests/enrichment/firecrawl.test.ts — Firecrawl Extractor

AttributeValue
Lines512
What it testsFirecrawl LLM-structured extraction pipeline: contact page discovery, mock-client full extraction, multi-page merge, HTML fallback, and error handling
Dependenciessrc/enrichment/sources/firecrawl.js
MocksInjected mock Firecrawl client via _setClientForTest()

Key Test Cases:

Describe BlockTestsAssertions
discoverContactPages — preloaded URLs4Accepts /kontakt, /contact, /om-oss, /team, /medarbetare, /ledning, /styrelse; excludes non-contact; empty when none found; caps at 3
CONTACT_PAGE_PATTERNS2Matches expected paths, rejects unrelated
discoverContactPages — depth filter3Accepts depth 1, accepts depth 2 for multilingual, rejects depth > 2
scrapeWebsiteFirecrawl — https:// guard1Strips protocol prefix to avoid https://https://
missing API key2Throws informative error when no FIRECRAWL_API_KEY
scrapeWebsiteFirecrawl with mock client14Output 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 merge2Merges contacts from 2 pages, uses headline from first successful page only
guessContactUrls4Returns 200-responding URLs, falls back to homepage on 404, caps at 3, handles fetch errors
extractContactsFromHtml — cheerio fallback7Finds 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 fallback2No crash when HTML undefined, uses HTML fallback when Firecrawl returns 0 contacts

16. tests/enrichment/processors.test.ts — Processor Utilities

AttributeValue
Lines183
What it testsPure function tests for name utilities, email utilities, phone extraction, and lead scoring
Dependenciessrc/enrichment/processors/nameUtils.js, emailUtils.js, phoneUtils.js, scoring.js
MocksNone — all pure functions

Key Test Cases:

Describe BlockTestsAssertions
inferRoleType8VD variants, CFO variants, default Övrig, PR→Marknadschef, compound split, Copywriter→Marknadschef, Rekryterare→Personalansvarig
normalizeName2Strips accents, André === Andre
maybeFlipSwedishName3Flips “Last First”, preserves normal order, no-op on 3-word
normalizeCompanyName2Strips AB/HB suffix
extractBrandName3Strips generic words + AB, undefined when no generic word, handles multiple generics
isValidPersonName4Accepts valid Swedish names, rejects single word, rejects AB, rejects email-like, rejects nav words
normSe1Normalizes Swedish chars (Å, Ä, Ö)
extractEmails2Extracts emails from text, deduplicates
generateEmailGuesses3Low confidence + generated_guess source, firstname.lastname@domain first, empty for single-word names
detectEmailPattern2Detects firstname.lastname pattern, detects initial pattern
extractPhones2Extracts Swedish mobile numbers with type, empty for no phones
calculateLeadScore3Returns 0 for empty, awards points for VD contact, caps at 10

Fetcher Tests (tests/fetchers/)

17. tests/fetchers/bolagsverket.test.ts — Bolagsverket Fetcher

AttributeValue
Lines113
What it testsZIP download orchestration: downloadAndExtract() coordinates download → extract → cleanup
Dependenciessrc/fetchers/bolagsverket/index.js, download.js, extract.js
Mocksaxios (download streaming), download.js, extract.js

Key Test Cases:

Describe BlockTestsAssertions
downloadFile1Placeholder — notes Bun mock.module limitations for testing actual vs mocked
downloadAndExtract2Orchestrates 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 destination
  • extractZip: Returns ['/tmp/extracted/file1.ixbrl', '/tmp/extracted/file2.ixbrl']
  • axios: Simulates streaming response with pipe + end events

18. tests/fetchers/scb.test.ts — SCB API Client

AttributeValue
Lines160
What it testsSCB PxWebApi v2 integration: table list, metadata, data fetch, and company synthesis
Dependenciessrc/fetchers/scb/index.js, src/fetchers/scb/types.js
Mocksaxios module (Bun mock.module) with fixture responses

Key Test Cases:

Describe BlockTestsAssertions
fetchTableList1Returns table list with pagination, correct table ID and label
fetchTableMetadata1Returns metadata with variables, correct SNI2007 variable
fetchTableData1Returns JSON-stat2 response with values and dimensions
fetchCompanies3Returns 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

AttributeValue
Lines17
What it testsDomain discovery guards against known bad domains (hosting providers, municipalities)
Dependenciessrc/enrichment/sources/domain.js (findCompanyDomain)
MocksNone — live domain discovery with real company names

Key Test Cases:

TestAssertion
Reject hosting providerfindCompanyDomain('Pontán Uppsala AB', '5569810061', 'Örsundsbro') does NOT return 180.se
Reject municipalityfindCompanyDomain('TMP I Uppsala AB', '5565478202', 'Uppsala') does NOT return uppsala.se

20. tests/speed.test.ts — Performance Benchmarks

AttributeValue
Lines22
What it testsRegistry lookup latency and correctness
Dependenciessrc/enrichment/sources/registry.js (getDomainFromRegistry), src/enrichment/sources/domain.js (findCompanyDomain)
MocksNone — tests against seeded database

Key Test Cases:

TestAssertion
Registry lookup for known entityReturns ['pontan.se'] if seeded, or empty array if not
Registry lookup latencyCompletes in under 50ms

Test Infrastructure Patterns

Common Patterns Across API Tests

  1. Auth Setup: Most API tests register a test user + org in beforeAll, falling back to login if already exists
  2. authApiRequest() Helper: Wraps fetch() with Authorization: Bearer <token> and Content-Type: application/json
  3. DB Cleanup: beforeEach / afterAll deletes test rows by source column or email pattern
  4. RoPA Verification: Direct PostgreSQL queries against RoPA_Log table verify audit compliance
  5. Pagination Assertions: All list endpoints return { data: [], pagination: { total, limit, offset, hasMore } }

Mocking Strategy

LayerApproachExamples
HTTP Clientbun:test mock.module('axios', ...)scb.test.ts, bolagsverket.test.ts
Internal Modulesmock.module('../../src/...', ...)bolagsverket.test.ts (download/extract)
External API ClientDependency injection (_setClientForTest)firecrawl.test.ts
Live ServerIntegration tests against localhost:3000All tests/api/*.test.ts
Pure FunctionsNo mocks — direct function callsprocessors.test.ts, crawlee-quality.test.ts

Environment Variables Used

VariableDefaultUsed In
PGHOSTlocalhostAll API + DB tests
PGPORT5433All API + DB tests
PGUSERuserAll API + DB tests
PGPASSWORDpasswordAll API + DB tests
PGDATABASEenrichnodedbAll API + DB tests
API_PORT3000All API tests
FIRECRAWL_API_KEYfirecrawl.test.ts (mocked)
JWT_SECRETsecurity.test.ts
KEYCLOAK_DEV_MODEkundkort-enrich.test.ts
BOLAGSVERKET_API_URLbolagsverket.test.ts
NODE_ENVsecurity.test.ts

Coverage Gaps & Recommendations

GapSuggested Test
No dedicated src/api/export.test.tsCSV/JSON export with GDPR filtering, large dataset handling
No dedicated src/api/scrape.test.tsOn-demand scraping endpoint
No src/workers/*.test.tsBullMQ worker job processing, error handling, retry logic
No src/compliance.test.ts in tests/hash_contact(), logRoPA() unit tests
No src/cache.test.tsRedis cache get/set, TTL behavior
No src/lib/smtpEmailValidator.test.tsSMTP validation logic (939-line module)
No frontend testsKundkort React components
No tests/api/export.test.tsExport endpoint security (CSV injection already covered in security.test.ts)