.. _testing_workspace_api: Testing with WorkspaceApiMock ============================== .. contents:: :depth: 4 Overview -------- Services that use workspace-scoped entities — such as ``ecos-uiserv`` — depend on ``WorkspaceWebApi``, which calls ``ecos-model`` over HTTP to resolve workspace membership. During ``@SpringBootTest`` tests ``ecos-model`` is not running, so ``WorkspaceWebApi.waitUntilEmodelApiIsAvailable()`` loops forever and tests hang indefinitely. The ``WorkspaceApiMock`` class, provided by ``ecos-webapp-lib-spring-test``, solves this problem. It implements ``WorkspaceApi`` entirely in-memory, performs no HTTP calls, and is auto-discovered by Spring Boot in test scope. Problem: Hanging Tests ---------------------- The call chain that triggers the hang: .. code-block:: text BoardRecord.getRef() → WorkspaceServiceImpl.addWsPrefixToId() → WorkspaceWebApi.mapIdentifiers() → WorkspaceWebApi.waitUntilEmodelApiIsAvailable() → infinite sleep loop Whenever a test exercises any code path that resolves a workspace-prefixed identifier, the whole test suite will stall unless a test-safe ``WorkspaceApi`` implementation is present. Solution: WorkspaceApiMock -------------------------- ``WorkspaceApiMock`` is shipped in the ``ecos-webapp-lib-spring-test`` module (in ``src/main``, so it is exported as part of the module artifact). It is annotated with ``@Component``, so Spring Boot's component scan picks it up automatically when the module is on the test classpath. ``ModelServiceFactoryInitializer`` was extended to optionally inject any ``WorkspaceApi`` bean: .. code-block:: kotlin @Autowired(required = false) var workspaceApi: WorkspaceApi? = null @PostConstruct fun init() { // ... other injections ... workspaceApi?.let { modelServices.setWorkspaceApi(it) } } When ``WorkspaceApiMock`` is present (test scope), it is injected and overrides ``WorkspaceWebApi``. In production deployments — where the test module is not on the classpath — ``ModelServiceFactory`` falls back to the default ``WorkspaceWebApi``. No production code is affected. Setup ----- Add ``ecos-webapp-lib-spring-test`` as a test-scoped dependency in your service's ``pom.xml``: .. code-block:: xml ru.citeck.ecos.webapp ecos-webapp-lib-spring-test test That is all. In any ``@SpringBootTest``, the ``WorkspaceApiMock`` bean is automatically discovered and injected into ``ModelServiceFactory``. No explicit ``@Import`` or additional configuration is required. Configuring Test Data --------------------- Inject ``WorkspaceApiMock`` directly into your test class to set up workspace members and managers: .. code-block:: kotlin @ExtendWith(EcosSpringExtension::class) @SpringBootTest(classes = [TestApp::class]) class MyWorkspaceTest { @Autowired lateinit var workspaceApiMock: WorkspaceApiMock @Autowired lateinit var workspaceService: WorkspaceService @BeforeEach fun setUp() { workspaceApiMock.clear() // always reset between tests } @Test fun someWorkspaceScopedTest() { workspaceApiMock.addMember("my-workspace", "john.doe") workspaceApiMock.addManager("my-workspace", "admin") val workspaces = workspaceService.getUserWorkspaces("john.doe") assertThat(workspaces).contains("my-workspace") } } Available methods on ``WorkspaceApiMock``: .. list-table:: :widths: 30 70 :header-rows: 1 :class: tight-table * - Method - Description * - ``addMember(workspace, user)`` - Registers ``user`` as a member of ``workspace``. * - ``addManager(workspace, user)`` - Registers ``user`` as a manager of ``workspace``. * - ``clear()`` - Removes all members and managers added so far. Default Behavior ---------------- When no data has been configured via ``addMember()`` / ``addManager()``: * ``getUserWorkspaces(user)`` returns ``setOf("user$")`` — the personal workspace, matching real ``WorkspaceService`` behavior. * ``getNestedWorkspaces(workspaces)`` returns empty sets for all input workspaces. * ``mapIdentifiers(ids, type)`` returns identifiers unchanged (identity mapping). * ``isUserManagerOf(user, workspace)`` returns ``false``. .. important:: ``WorkspaceApiMock`` is a singleton Spring bean shared across all tests in a test run. State configured in one test persists into the next. **Always call** ``workspaceApiMock.clear()`` in ``@BeforeEach`` to ensure test isolation. Guarding Against Regressions ----------------------------- To explicitly protect against any future regression that re-introduces a hang, annotate time-sensitive tests with ``@Timeout``: .. code-block:: kotlin @Test @Timeout(value = 10, unit = TimeUnit.SECONDS) fun workspacePrefixShouldNotHang() { val result = workspaceService.addWsPrefixToId("local-id", "ws1") assertThat(result).isNotNull() } If the underlying code ever starts blocking again, the test will fail with a timeout error rather than hanging the entire CI pipeline. Module Structure Note --------------------- ``WorkspaceApiMock`` lives in ``src/main`` of the ``ecos-webapp-lib-spring-test`` module — not in ``src/test``. This distinction is intentional: * **``src/main``** — code that is compiled into the module artifact and exported to consumers' test classpaths. * **``src/test``** — internal tests of the module itself (e.g., ``WorkspaceApiMockTest``). Any ``@Component`` or ``@Configuration`` class that must be auto-discovered by consuming services' Spring Boot tests must reside in ``src/main`` of the test module. See Also -------- * :ref:`Creating a new microservice ` — general guide on setting up a Spring Boot microservice with Citeck. * :ref:`Demo microservice ` — a working example of a Citeck microservice with tests.