Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **Tests**: `UserResourceTest` stores a fixed bcrypt hash in setup. The password update still asserts the stored hash changes ([#321](https://github.com/sitmun/sitmun-backend-core/issues/321)).
- **Tests**: `DashboardMetricsQueryTest` uses the suite database. Its connections do not autocommit, so the transactional slice removes its rows. The day bucket still checks year, month, and day, and its count rises by one. Per-application counts stay `1` or `0` on an `app-` plus UUID name, including the built-in public user ([#322](https://github.com/sitmun/sitmun-backend-core/issues/322)).
- **Tests**: `UserResourceTest.createUser` checks that GET after delete returns 404. `UserResourceIntegrationTest` no longer repeats that create and delete on a real server ([#313](https://github.com/sitmun/sitmun-backend-core/issues/313)).
- **Tests**: `ServiceCheckStoreTest`, `AffectedApplicationsTest`, `ServiceHealthFilterTest`, `ServiceAccessSummaryTest`, and `ServiceCheckDispatcherTest` load `ServiceCheckSliceConfiguration` on the suite database, so Liquibase runs once for that slice. Connections do not autocommit, so the transactional slice removes its rows ([#317](https://github.com/sitmun/sitmun-backend-core/issues/317)).

### Security

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
Expand All @@ -13,16 +14,12 @@
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -35,27 +32,29 @@
import org.sitmun.domain.service.check.ServiceCheckRepository;
import org.sitmun.domain.service.check.ServiceCheckSample;
import org.sitmun.domain.service.check.ServiceCheckSampleRepository;
import org.sitmun.domain.service.check.ServiceCheckSliceConfiguration;
import org.sitmun.domain.service.check.ServiceCheckSliceConfiguration.ProbeExecutor;
import org.sitmun.domain.service.check.ServiceCheckStore;
import org.sitmun.domain.service.usage.ServiceUsage;
import org.sitmun.domain.service.usage.ServiceUsageRepository;
import org.sitmun.domain.service.usage.ServiceUsageStore;
import org.sitmun.infrastructure.persistence.type.i18n.I18nTestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(LiquibaseAutoConfiguration.class)
@TestPropertySource(properties = "sitmun.service-check.enabled=true")
@Import(ServiceCheckSliceConfiguration.class)
@TestPropertySource(
properties = {
"sitmun.service-check.enabled=true",
"spring.datasource.hikari.auto-commit=false"
})
@DisplayName("Service check dispatcher")
class ServiceCheckDispatcherTest {

Expand All @@ -71,12 +70,13 @@ class ServiceCheckDispatcherTest {
@Autowired private ServiceCheckSampleRepository sampleRepository;
@Autowired private ServiceUsageRepository usageRepository;
@Autowired private ApplicationRepository applicationRepository;
@MockitoBean private ServiceAccessCheckExecutor checks;
@Autowired private ServiceAccessCheckExecutor checks;

private CountDownLatch releaseProbe;

@BeforeEach
void resetWorkers() {
reset(checks);
workers.async(false);
releaseProbe = new CountDownLatch(1);
Instant fresh = clock.instant();
Expand Down Expand Up @@ -326,61 +326,4 @@ private ServiceCheckSample sample(Service service, Instant observedAt, String st
.observedAt(observedAt)
.build();
}

@TestConfiguration
@EnableConfigurationProperties(ServiceCheckProperties.class)
@Import({
I18nTestConfiguration.class,
ServiceCheckStore.class,
ServiceUsageStore.class,
ServiceCheckDueQuery.class,
ServiceCheckDispatcher.class
})
static class Configuration {

@Bean
Clock serviceCheckClock() {
return Clock.fixed(Instant.parse("2026-10-05T12:00:00Z"), ZoneOffset.UTC);
}

@Bean
ServiceCheckInFlight serviceCheckInFlight(ServiceCheckProperties properties) {
return new ServiceCheckInFlight(properties.maxInFlight());
}

@Bean(destroyMethod = "close")
ProbeExecutor serviceCheckExecutor() {
return new ProbeExecutor();
}
}

static final class ProbeExecutor implements Executor, AutoCloseable {

private final ExecutorService pool =
Executors.newCachedThreadPool(
runnable -> {
Thread thread = new Thread(runnable, "service-check-test");
thread.setDaemon(true);
return thread;
});
private final AtomicBoolean async = new AtomicBoolean();

void async(boolean enabled) {
async.set(enabled);
}

@Override
public void execute(Runnable command) {
if (async.get()) {
pool.execute(command);
} else {
command.run();
}
}

@Override
public void close() {
pool.shutdownNow();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.sitmun.domain.cartography.permission.CartographyPermissionRepository;
import org.sitmun.domain.role.Role;
import org.sitmun.domain.role.RoleRepository;
import org.sitmun.domain.service.check.ServiceCheckSliceConfiguration;
import org.sitmun.domain.service.check.ServiceCheckStore;
import org.sitmun.domain.service.usage.ServiceUsage;
import org.sitmun.domain.service.usage.ServiceUsageKey;
Expand All @@ -38,21 +39,24 @@
import org.sitmun.domain.tree.TreeRepository;
import org.sitmun.domain.tree.node.TreeNode;
import org.sitmun.domain.tree.node.TreeNodeRepository;
import org.sitmun.infrastructure.persistence.type.i18n.I18nTestConfiguration;
import org.sitmun.upstream.signal.ServiceStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.test.context.TestPropertySource;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(LiquibaseAutoConfiguration.class)
@Transactional
@Import(ServiceCheckSliceConfiguration.class)
@TestPropertySource(
properties = {
"sitmun.service-check.enabled=true",
"spring.datasource.hikari.auto-commit=false"
})
@DisplayName("Affected applications")
class AffectedApplicationsTest {

Expand Down Expand Up @@ -275,8 +279,4 @@ private void recordUsage(
row.setRequests(requests);
usage.save(row);
}

@TestConfiguration
@Import({I18nTestConfiguration.class, ServiceCheckStore.class, AffectedApplications.class})
static class Configuration {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,26 @@
import org.sitmun.administration.service.access.ServiceAccessSummary;
import org.sitmun.domain.service.Service;
import org.sitmun.domain.service.ServiceRepository;
import org.sitmun.domain.service.usage.ServiceUsageQueries;
import org.sitmun.infrastructure.persistence.type.i18n.I18nTestConfiguration;
import org.sitmun.upstream.signal.ServiceStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.TestPropertySource;
import org.springframework.web.server.ResponseStatusException;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(LiquibaseAutoConfiguration.class)
@Import(ServiceCheckSliceConfiguration.class)
@TestPropertySource(
properties = {
"sitmun.service-check.enabled=true",
"spring.datasource.hikari.auto-commit=false"
})
@DisplayName("Service access summaries")
class ServiceAccessSummaryTest {

Expand Down Expand Up @@ -325,13 +329,4 @@ private Service save(String name) {
.isProxied(false)
.build());
}

@TestConfiguration
@Import({
I18nTestConfiguration.class,
ServiceCheckStore.class,
ServiceAccessSummaries.class,
ServiceUsageQueries.class
})
static class Configuration {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.sitmun.domain.service.check;

import static org.mockito.Mockito.mock;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.sitmun.administration.service.access.ServiceAccessCheckExecutor;
import org.sitmun.administration.service.access.ServiceAccessSummaries;
import org.sitmun.administration.service.access.ServiceCheckDispatcher;
import org.sitmun.administration.service.access.ServiceCheckDueQuery;
import org.sitmun.administration.service.access.ServiceCheckInFlight;
import org.sitmun.administration.service.access.ServiceCheckProperties;
import org.sitmun.domain.service.AffectedApplications;
import org.sitmun.domain.service.ServiceHealthQuery;
import org.sitmun.domain.service.usage.ServiceUsageQueries;
import org.sitmun.domain.service.usage.ServiceUsageStore;
import org.sitmun.infrastructure.persistence.type.i18n.I18nTestConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;

@TestConfiguration
@EnableConfigurationProperties(ServiceCheckProperties.class)
@Import({
I18nTestConfiguration.class,
ServiceCheckStore.class,
ServiceHealthQuery.class,
AffectedApplications.class,
ServiceAccessSummaries.class,
ServiceUsageQueries.class,
ServiceUsageStore.class,
ServiceCheckDueQuery.class,
ServiceCheckDispatcher.class
})
public class ServiceCheckSliceConfiguration {

@Bean
ServiceAccessCheckExecutor serviceAccessCheckExecutor() {
return mock(ServiceAccessCheckExecutor.class);
}

@Bean
Clock serviceCheckClock() {
return Clock.fixed(Instant.parse("2026-10-05T12:00:00Z"), ZoneOffset.UTC);
}

@Bean
ServiceCheckInFlight serviceCheckInFlight(ServiceCheckProperties properties) {
return new ServiceCheckInFlight(properties.maxInFlight());
}

@Bean(destroyMethod = "close")
ProbeExecutor serviceCheckExecutor() {
return new ProbeExecutor();
}

public static final class ProbeExecutor implements Executor, AutoCloseable {

private final ExecutorService pool =
Executors.newCachedThreadPool(
runnable -> {
Thread thread = new Thread(runnable, "service-check-test");
thread.setDaemon(true);
return thread;
});
private final AtomicBoolean async = new AtomicBoolean();

public void async(boolean enabled) {
async.set(enabled);
}

@Override
public void execute(Runnable command) {
if (async.get()) {
pool.execute(command);
} else {
command.run();
}
}

@Override
public void close() {
pool.shutdownNow();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@
import org.junit.jupiter.api.Test;
import org.sitmun.domain.service.Service;
import org.sitmun.domain.service.ServiceRepository;
import org.sitmun.infrastructure.persistence.type.i18n.I18nTestConfiguration;
import org.sitmun.upstream.signal.ServiceStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(LiquibaseAutoConfiguration.class)
@Import(ServiceCheckSliceConfiguration.class)
@TestPropertySource(
properties = {
"sitmun.service-check.enabled=true",
"spring.datasource.hikari.auto-commit=false"
})
@DisplayName("Service check store")
class ServiceCheckStoreTest {

Expand Down Expand Up @@ -84,8 +89,4 @@ void upsertReplacesLatestAndLeavesBlockedUnchanged() {
Service reloaded = serviceRepository.findById(service.getId()).orElseThrow();
assertThat(reloaded.getBlocked()).isTrue();
}

@TestConfiguration
@Import({I18nTestConfiguration.class, ServiceCheckStore.class})
static class Configuration {}
}
Loading
Loading