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
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
*/
@Testcontainers(disabledWithoutDocker = true)
class AuditLedgerContractGanacheTest {
private static final Instant FIXED_EVENT_TIME = Instant.parse("2026-05-15T10:15:30Z");

/**
* Deterministic account 0 private key for Ganache mnemonic
Expand Down Expand Up @@ -111,7 +112,7 @@ static void shutdown() {
@Test
void appendAuditRecord_anchorsHashAndIsHashExistsConfirmsIt() {
byte[] hash = randomHash();
BigInteger timestamp = BigInteger.valueOf(Instant.now().getEpochSecond());
BigInteger timestamp = BigInteger.valueOf(FIXED_EVENT_TIME.getEpochSecond());

assertThat(contract.isHashExists(hash))
.as("hash must not exist before anchoring")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
*/
@ExtendWith(MockitoExtension.class)
class BlockchainWriterServiceTest {
private static final Instant FIXED_NOW = Instant.parse("2026-05-15T10:15:30Z");

@Mock private Web3j web3j;
@Mock private Credentials credentials;
Expand Down Expand Up @@ -300,7 +301,7 @@ void anchorEvent_failsFastOnMissingEventId() {
// eventId is null; occurredAt and eventType are valid → should fail on eventId check
UserLoggedInEvent event = UserLoggedInEvent.builder().build();
event.setEventType(EventType.USER_LOGGED_IN);
event.setOccurredAt(Instant.now());
event.setOccurredAt(FIXED_NOW);

assertThatThrownBy(() -> service.anchorEvent(event))
.isInstanceOf(BlockchainWriterService.NonRecoverableEventException.class)
Expand All @@ -312,7 +313,7 @@ void anchorEvent_failsFastOnMissingEventType() {
// eventId and occurredAt are valid; eventType is null → should fail on eventType check
UserLoggedInEvent event = UserLoggedInEvent.builder().build();
event.setEventId("00000000-0000-0000-0000-000000000001");
event.setOccurredAt(Instant.now());
event.setOccurredAt(FIXED_NOW);
// eventType intentionally left null

assertThatThrownBy(() -> service.anchorEvent(event))
Expand Down Expand Up @@ -370,7 +371,7 @@ void anchorEvent_allowsNullUserIdToStayAlignedWithEventStoreFallbacks() {
void anchorEvent_allowsFutureTimestampBeyondDefaultToleranceToStayAlignedWithEventStore() {
// Even when beyond tolerance, audit-writer should not DLT the record only due to clock skew.
UserLoggedInEvent event = UserLoggedInEvent.of("u1", null, null);
event.setOccurredAt(Instant.now().plusSeconds(360));
event.setOccurredAt(FIXED_NOW.plusSeconds(360));

TransactionReceipt receipt = new TransactionReceipt();
receipt.setStatus("0x1");
Expand Down Expand Up @@ -425,7 +426,7 @@ void anchorEvent_rejectsEventIdWithWrongLength() {
void anchorEvent_acceptsFutureTimestampWithinDefaultTolerance() {
// Default tolerance is 300 seconds; use 299 seconds in future
UserLoggedInEvent event = UserLoggedInEvent.of("u1", null, null);
event.setOccurredAt(Instant.now().plusSeconds(299));
event.setOccurredAt(FIXED_NOW.plusSeconds(299));

TransactionReceipt receipt = new TransactionReceipt();
receipt.setStatus("0x1");
Expand All @@ -448,7 +449,7 @@ void anchorEvent_acceptsFutureTimestampWithinDefaultTolerance() {
void anchorEvent_allowsFutureTimestampBeyondCustomToleranceToStayAlignedWithEventStore() {
props.setFutureTimestampToleranceSeconds(60);
UserLoggedInEvent event = UserLoggedInEvent.of("u1", null, null);
event.setOccurredAt(Instant.now().plusSeconds(120));
event.setOccurredAt(FIXED_NOW.plusSeconds(120));

TransactionReceipt receipt = new TransactionReceipt();
receipt.setStatus("0x1");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package lt.satsyuk.distributed.audit.command.config;

import lt.satsyuk.distributed.audit.event.AuditEvent;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -13,7 +13,7 @@
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.kafka.support.serializer.JsonSerializer;
import org.springframework.kafka.support.serializer.JacksonJsonSerializer;

import java.util.Map;
import java.util.HashMap;
Expand All @@ -39,7 +39,7 @@ public ProducerFactory<String, AuditEvent> auditEventProducerFactory(
mergeKafkaOverrides(props, environment);
props.putIfAbsent(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.putIfAbsent(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.putIfAbsent(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
props.putIfAbsent(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JacksonJsonSerializer.class);
props.putIfAbsent(JSON_ADD_TYPE_HEADERS_CONFIG, false);
return new DefaultKafkaProducerFactory<>(props);
}
Expand All @@ -54,19 +54,34 @@ public KafkaTemplate<String, AuditEvent> auditEventKafkaTemplate(

private static void mergeKafkaOverrides(Map<String, Object> props,
ConfigurableEnvironment environment) {
Binder binder = Binder.get(environment);
for (PropertySource<?> propertySource : environment.getPropertySources()) {
if (propertySource instanceof EnumerablePropertySource<?> enumerablePropertySource) {
mergeKafkaOverridesFromPropertySource(props, enumerablePropertySource, environment);
}
}
}

binder.bind("spring.kafka.properties", Bindable.mapOf(String.class, String.class))
.ifBound(props::putAll);
private static void mergeKafkaOverridesFromPropertySource(Map<String, Object> props,
EnumerablePropertySource<?> propertySource,
ConfigurableEnvironment environment) {
for (String propertyName : propertySource.getPropertyNames()) {
if (propertyName.startsWith("spring.kafka.properties.")) {
String kafkaKey = propertyName.substring("spring.kafka.properties.".length());
putIfPresent(props, kafkaKey, environment.getProperty(propertyName));
} else if (propertyName.startsWith("spring.kafka.producer.")) {
String producerKey = propertyName.substring("spring.kafka.producer.".length());
String kafkaKey = producerKey.startsWith("properties.")
? producerKey.substring("properties.".length())
: producerKey.replace('-', '.');
putIfPresent(props, kafkaKey, environment.getProperty(propertyName));
}
}
}

binder.bind("spring.kafka.producer", Bindable.mapOf(String.class, String.class))
.ifBound(m -> m.forEach((key, value) -> {
if (key.startsWith("properties.")) {
props.put(key.substring("properties.".length()), value);
} else {
props.put(key.replace('-', '.'), value);
}
}));
private static void putIfPresent(Map<String, Object> props, String key, String value) {
if (value != null) {
props.put(key, value);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
class KafkaProducerConfigIntegrationTest {

private static final String JSON_SERIALIZER_FQCN =
"org.springframework.kafka.support.serializer.JsonSerializer";
"org.springframework.kafka.support.serializer.JacksonJsonSerializer";

private final KafkaTemplate<String, AuditEvent> auditEventKafkaTemplate;
private final ProducerFactory<String, AuditEvent> auditEventProducerFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.assertj.core.api.Assertions.assertThat;

class JwtSecurityComponentsTest {
private static final Instant FIXED_ISSUED_AT = Instant.parse("2099-05-15T10:15:30Z");

@Test
void bearerTokenConverterExtractsTokenFromAuthorizationHeader() {
Expand Down Expand Up @@ -52,7 +53,7 @@ void bearerTokenConverterAcceptsLowercaseBearerScheme() {
@Test
void jwtAuthenticationManagerMapsRolesToGrantedAuthorities() {
JwtService jwtService = new JwtService("shared-security-test-secret-123456", "dal-test", Duration.ofMinutes(30));
String token = jwtService.generateToken("auditor", Set.of(UserRole.AUDITOR, UserRole.ADMIN), Instant.now());
String token = jwtService.generateToken("auditor", Set.of(UserRole.AUDITOR, UserRole.ADMIN), FIXED_ISSUED_AT);

JwtTokenReactiveAuthenticationManager authenticationManager =
new JwtTokenReactiveAuthenticationManager(jwtService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand Down Expand Up @@ -72,7 +73,7 @@ void persistMapsUserLoginEventToEntityAndStoresHash() {
assertEquals(64, saved.getEventHash().length());
assertEquals("661fa908d63fb896bf2f5b8aca9b0db207a73e99d04af2216005bdb98237607b", saved.getEventHash());
assertNotNull(saved.getCreatedAt());
assertEquals(LocalDateTime.of(2026, 5, 15, 10, 15, 30), saved.getCreatedAt());
assertEquals(LocalDateTime.of(2026, Month.MAY, 15, 10, 15, 30), saved.getCreatedAt());

ArgumentCaptor<StoredAuditEvent> captor = ArgumentCaptor.forClass(StoredAuditEvent.class);
verify(repository).save(captor.capture());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class IntegrityCheckIntegrationTest {

/** Valid 64-char hex hash used across multiple test cases. */
private static final String HASH_64 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
private static final Instant FIXED_ISSUED_AT = Instant.parse("2099-05-15T10:15:30Z");
private final JwtService jwtService;

@SuppressWarnings("resource")
Expand Down Expand Up @@ -321,7 +322,7 @@ private String bearerToken(UserRole... roles) {
return "Bearer " + jwtService.generateToken(
"integration-user",
Set.of(roles),
Instant.now()
FIXED_ISSUED_AT
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.mapstruct.factory.Mappers;

import java.time.LocalDateTime;
import java.time.Month;

import static org.junit.jupiter.api.Assertions.assertEquals;

Expand All @@ -23,7 +24,7 @@ void toDtoMapsCoreFields() {
eventRecord.setUserId("user-15");
eventRecord.setPayload("{\"userId\":\"user-15\"}");
eventRecord.setEventHash("abc123xyz");
eventRecord.setCreatedAt(LocalDateTime.of(2026, 5, 15, 10, 30, 0));
eventRecord.setCreatedAt(LocalDateTime.of(2026, Month.MAY, 15, 10, 30, 0));

AuditEventDto dto = mapper.toDto(eventRecord);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -126,7 +127,7 @@ private AuditEventRecord sampleRecord() {
eventRecord.setEventId("event-101");
eventRecord.setEventType("USER_LOGGED_IN");
eventRecord.setUserId("user-1");
eventRecord.setCreatedAt(LocalDateTime.of(2026, 5, 16, 10, 0));
eventRecord.setCreatedAt(LocalDateTime.of(2026, Month.MAY, 16, 10, 0));
eventRecord.setPayload("{\"userId\":\"user-1\"}");
return eventRecord;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,44 @@ <h3>Payload</h3>
<mat-label>Payload search</mat-label>
<input
matInput
id="audit-payload-search"
[formControl]="searchControl"
placeholder="Search event payload text"
aria-label="Payload search"
(keyup.enter)="applyFilters()"
/>
<mat-icon matSuffix aria-hidden="true">search</mat-icon>
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>Event type</mat-label>
<input matInput [formControl]="eventTypeControl" placeholder="USER_LOGGED_IN" (keyup.enter)="applyFilters()" />
<input
matInput
id="audit-event-type"
[formControl]="eventTypeControl"
placeholder="USER_LOGGED_IN"
aria-label="Event type"
(keyup.enter)="applyFilters()"
/>
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>User ID</mat-label>
<input matInput [formControl]="userIdControl" placeholder="user1" (keyup.enter)="applyFilters()" />
<input
matInput
id="audit-user-id"
[formControl]="userIdControl"
placeholder="user1"
aria-label="User ID"
(keyup.enter)="applyFilters()"
/>
</mat-form-field>

<mat-form-field appearance="outline" class="filter-field filter-field--wide">
<mat-label>Date range</mat-label>
<mat-date-range-input [formGroup]="dateRangeForm" [rangePicker]="rangePicker">
<input matStartDate formControlName="from" placeholder="From date" />
<input matEndDate formControlName="to" placeholder="To date" />
<input matStartDate id="audit-from-date" formControlName="from" placeholder="From date" aria-label="From date" />
<input matEndDate id="audit-to-date" formControlName="to" placeholder="To date" aria-label="To date" />
</mat-date-range-input>
<mat-datepicker-toggle matIconSuffix [for]="rangePicker"></mat-datepicker-toggle>
<mat-date-range-picker #rangePicker></mat-date-range-picker>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ <h2>Sign In</h2>
<form [formGroup]="form" (ngSubmit)="submit()" class="login-form" autocomplete="on">
<mat-form-field appearance="outline">
<mat-label>Username</mat-label>
<input matInput id="username" name="username" type="text" formControlName="username" autocomplete="username" />
<input matInput id="username" name="username" type="text" formControlName="username" autocomplete="username" aria-label="Username" />
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>Password</mat-label>
<input matInput id="password" name="password" type="password" formControlName="password" autocomplete="current-password" />
<input matInput id="password" name="password" type="password" formControlName="password" autocomplete="current-password" aria-label="Password" />
</mat-form-field>

@if (errorMessage()) {
Expand Down