Test Documentation
Test Strategy
MetaOne uses a layered test approach:
| Layer | Framework | Scope |
|---|---|---|
| Unit tests | JUnit 5 + Mockito | Individual service/component logic |
| Integration tests | Spring Boot Test + H2 | Full Spring context, in-memory DB |
| End-to-end tests | Spring Boot Test | Full install/enable/invoke/event lifecycle |
| Webhook tests | MockMvc + Mockito | HTTP-level controller tests |
All tests run with the test Spring profile, which uses H2 in PostgreSQL compatibility mode — no external database is required.
Running Tests
# Full test suite (all modules)
mvn test
# Single module
mvn -pl metaone-core test
mvn -pl crm-extension test
mvn -pl chatwoot-extension test
# Single test class
mvn -pl metaone-core -Dtest=ExtensionInstallerServiceTest test
mvn -pl metaone-core -Dtest=CommonResponseDTOTest test
mvn -pl metaone-core -Dtest=InProcessEventGatewayTest test
# Skip tests during build
mvn -DskipTests package⚠️ Known issue: Full
mvn testmay fail on Spring context startup in some environments. Runmvn -pl metaone-core -Dtest=ExtensionInstallerServiceTest testas the reliable baseline. See TODOS.md P1.
Test Coverage by Area
Extension Lifecycle (metaone-core)
| Test Class | What It Covers |
|---|---|
ExtensionInstallerServiceTest | Install, uninstall, enable, disable flows; status transitions; error handling |
ExtensionLifecycleE2ETest | Full end-to-end: install → enable → invoke capability → disable → uninstall |
ExtensionRuntimeServiceImplTest | PF4J plugin load/unload, health checks |
ExtensionRuntimeBootstrapTest | Plugin bootstrap on startup |
ExtensionInstallerEventHookTest | Event subscription hooks during install/enable/disable |
ExtensionUpdaterServiceImplTest | Version update flow; rollback on failure |
ExtensionHealthCheckServiceImplTest | Health check pass/fail scenarios |
CompatibilityServiceImplTest | Platform version compatibility checks |
Security & Policy (metaone-core)
| Test Class | What It Covers |
|---|---|
ExtensionPolicyEvaluatorImplTest | Install policy: blocked categories, duplicate installs |
SignatureVerificationServiceImplTest | Artifact signature validation (pass/fail/missing) |
Event System (metaone-core)
| Test Class | What It Covers |
|---|---|
InProcessEventGatewayTest | Async/sync dispatch, @EventHandler discovery, typed payload deserialization, WorkspaceContext propagation |
Configuration (metaone-core)
| Test Class | What It Covers |
|---|---|
ConfigEncryptionServiceTest | AES-256-GCM encrypt/decrypt, key rotation |
ExtensionConfigServiceImplTest | CRUD config values, secret masking, required field validation |
ExtensionConfigIntegrationTest | Full config round-trip: save → retrieve → validate → enable |
HTTP Layer (metaone-core)
| Test Class | What It Covers |
|---|---|
ExtensionWebhookControllerTest | Webhook routing, WebhookAuthenticationException → HTTP 401, missing handler → 404 |
CommonResponseDTOTest | Response wrapper construction, null field exclusion |
Data Layer (metaone-core)
| Test Class | What It Covers |
|---|---|
WorkspaceDataSourceRegistryTest | Workspace DataSource pool creation, eviction, max-pool enforcement |
WorkspaceMembershipRepositoryTest | Membership queries, cascade behavior |
Chatwoot Extension (chatwoot-extension)
| Test Class | What It Covers |
|---|---|
ChatwootWebhookHandlerTest | HMAC-SHA256 validation, event type routing, WebhookAuthenticationException on bad signature |
ChatwootWidgetHandlerTest | Widget config and identity token capability handlers |
Test Configuration
Tests use src/test/resources/application-test.yaml:
spring:
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
database-platform: org.hibernate.dialect.H2DialectH2's PostgreSQL compatibility mode handles most PostgreSQL-specific syntax. Schema is created fresh for each test run and dropped on exit.
Test Patterns
Mocking External Dependencies
Services with external side effects (runtime deploy, health checks) are mocked in tests:
@MockitoBean
private ExtensionRuntimeService extensionRuntimeService;
@MockitoBean
private ExtensionHealthCheckService healthCheckService;
@BeforeEach
void setUp() {
doNothing().when(extensionRuntimeService).deploy(any(), any(), any());
doNothing().when(healthCheckService).assertHealthy(anyString(), any());
}Integration Test Pattern
@SpringBootTest
@ActiveProfiles("test")
@Transactional // rolls back after each test
class ExtensionInstallerServiceTest {
@Autowired ExtensionInstallerService installerService;
@Autowired WorkspaceRepository workspaceRepository;
@Test
void testInstallExtension() {
Workspace ws = workspaceRepository.save(...);
ExtensionInstallation result = installerService.install("crm-extension", "1.0.0", ws.getId());
assertEquals(ExtensionStatus.INSTALLED, result.getStatus());
}
}Known Coverage Gaps
The following areas have zero test coverage and are tracked as tech debt:
| Area | Tracked In |
|---|---|
CrmService.publishOpportunityEvent | TODOS P2 |
NotificationEventListener.onNotification | TODOS P2 |
NotificationEventListener.resolveChannel | TODOS P2 |
ChatService.sendSystemMessage | TODOS P2 |
ChatService.getOrCreateDealsChannel (race condition path) | TODOS P2 |
Starting point for new coverage: Follow the pattern in InProcessEventGatewayTest for event dispatch testing. For the getOrCreateDealsChannel race condition, spin two threads and verify idempotency via DataIntegrityViolationException handling.
Quality Gates
Before merging, ensure:
- [ ]
mvn -DskipTests packagesucceeds (compilation + assembly) - [ ] All tests in the changed module pass:
mvn -pl <module> test - [ ] No new
RuntimeExceptionthrown directly in service code (use typed exceptions) - [ ] New capability handlers return
CapabilityResult<T>(not raw types) - [ ]
CHANGELOG.mdupdated for user-facing changes