# Testing Strategy & Guide
## Test Categories
### Unit Tests (104 tests)
Located in `#[cfg(test)] mod tests` within each source file.
| `models::place` | 6 | Construction, defaults, address/geo, serialization, soft delete |
| `models::address` | 4 | Construction, fields, serialization, partial |
| `models::geo` | 7 | Construction, elevation, Haversine (same point, known distance, short, antipodal), serialization |
| `models::place_type` | 4 | Display, equality, serialization, Other variant |
| `models::identifier` | 3 | GLN, custom, serialization |
| `models::amenity` | 2 | Construction, with value |
| `models::opening_hours` | 2 | Construction, serialization |
| `models::consent` | 4 | Active, revoked, expired by date, not yet expired |
| `matching::name` | 8 | Exact, case-insensitive, similar, different, empty, both empty, substring, prefix bonus |
| `matching::address` | 5 | Identical, different, partial, no overlap, case-insensitive |
| `matching::geo` | 7 | Same point, close, moderate, far, within radius (true/false), custom reference |
| `matching::identifier` | 7 | Matching/different GLN, empty, mixed, has_gln_match (true, false type, false value) |
| `matching::phonetic` | 10 | Robert, Rupert, match, no match, Ashcraft, empty, single char, case, Washington, place names |
| `matching::scoring` | 8 | Identical places, name only, different, GLN deterministic, confidence levels, weights sum, fuzzy, phonetic bonus |
| `validation` | 19 | Valid place, empty/whitespace name, invalid lat/lon, valid coords, invalid/valid GLN, invalid/valid URL, invalid/valid telephone, address missing fields, address with locality, multiple errors, normalization |
| `privacy` | 8 | Mask telephone/fax/geo, preserve name, no sensitive fields, short phone, GDPR export, export fields |
### Integration Tests (67 tests)
Located in `tests/` directory. Test end-to-end workflows and edge cases.
| `integration_matching.rs` | 7 | Exact duplicate, typo match, completely different, same name different city, GLN override, name only, batch candidates |
| `integration_validation.rs` | 3 | Validate-normalize workflow, invalid place handling, full lifecycle |
| `integration_privacy.rs` | 4 | Mask-export workflow, full GDPR export, immutability, soft delete export |
| `integration_models.rs` | 16 | Full construction serialization, soft delete timestamps, unique IDs, place hierarchy, geo distance symmetry/triangle inequality, multiple identifier types, consent lifecycle/serialization, all place types, full week opening hours, address default/equality |
| `integration_scoring.rs` | 24 | Unicode names, long names, single char, reversed words, address edge cases, geo poles/date line/radius boundary, identifier edge cases, Soundex consistency, custom weights, confidence boundaries, score range validation, phonetic bonus, all components, batch sorting |
| `integration_edge_cases.rs` | 13 | Boundary coordinates, GLN length validation, URL protocols, address minimal/empty fields, multi-word normalization, idempotent normalization, all sensitive fields masking, empty phone masking, GDPR field preservation, combined workflows, GLN deterministic override |
### Benchmark Tests (16 benchmarks)
Located in `benches/` directory. Uses Criterion for statistical benchmarking.
| `matching_bench.rs` | 9 | name_similarity (exact/fuzzy/different), geo_similarity (close/far), soundex (short/long), full_place_match, batch_match_100 |
| `validation_bench.rs` | 3 | validate_simple, validate_full, normalize_place |
| `searching_bench.rs` | 2 | search_by_name_100, search_by_name_fuzzy_100 |
| `database_reading_bench.rs` | 2 | place_construction, place_batch_construction_100 |
| `database_writing_bench.rs` | 2 | place_create_and_validate, place_create_and_normalize |
| `privacy_bench.rs` | 4 | mask_place, mask_place_minimal, gdpr_export, gdpr_export_batch_100 |
## Running Tests
```bash
# All tests
cargo test
# Unit tests only
cargo test --lib
# Specific module
cargo test --lib models::place
cargo test --lib matching::scoring
# Integration tests only
cargo test --tests
# Specific integration test
cargo test --test integration_matching
# With output
cargo test -- --nocapture
# Benchmarks
cargo bench
# Specific benchmark
cargo bench -- name_similarity
```
## Writing New Tests
### Unit Test Pattern
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_descriptive_name() {
// Arrange
let input = setup_test_data();
// Act
let result = function_under_test(input);
// Assert
assert_eq!(result, expected);
}
}
```
### Integration Test Pattern
```rust
// tests/integration_feature.rs
use place_service::models::place::Place;
#[test]
fn test_end_to_end_workflow() {
// Setup
let place = Place::new("Test");
// Execute pipeline
let validated = validate_place(&place);
let matched = compute_match(&place, &other, &weights);
// Verify
assert!(validated.is_empty());
assert!(matched.score > 0.8);
}
```
## Test Data Conventions
- Use well-known places for readability (Central Park, Eiffel Tower, etc.)
- Use realistic coordinates (NYC: 40.7829, -73.9654)
- Use valid GLN format for identifier tests (13 digits)
- Use `Place::new("name")` for simple test places
## Bridge Integration Tests
`tests/duplicate_detection.rs` is a black-box test that drives the
service-side domain model through [`matching::adapter::to_matcher_place`]
and asserts on `MatchingEngine::match_places` output. The suite pins
**both sides of the contract** — the adapter's field-routing rules and
the matcher's scoring algorithm — so a regression on either side fails
a test here.
Run with: `cargo test --test duplicate_detection`
### Coverage (14 tests)
| Identical / near-duplicate | identical-clone score ≥ 0.95, name-typo fuzzy match, ordering invariants (closer-evidence outscores farther) |
| Deterministic short-circuits | GLN deterministic short-circuit, OSM identifier → `OsmNode` scheme, geo-distance ranking (closer > farther), PlaceType match/mismatch, antipodal-coordinates negative case |
| Negative cases | unrelated records score low, common-name + divergent demographics not flagged as duplicate |
| Field-routing pinning | per-adapter mapping tests (telecom → phone/email, address field renames, identifier-system-URI routing) |
| Edge cases | sparse records, empty fields, config presets |
### Running
```bash
cargo test --test duplicate_detection # all bridge tests
cargo test --test duplicate_detection identical # just the identical-clone tests
cargo test --test duplicate_detection -- --nocapture # with stdout
```
### When to add a new test here
Add a bridge test when:
- The adapter (`src/matching/adapter.rs`) gains a new routing rule.
- The place-matcher crate exposes a new scoring component the service
needs to surface.
- A regression escapes the adapter's own `#[cfg(test)] mod tests`.