Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 1301 fix quarter generation #1311

Merged
merged 13 commits into from
Jan 22, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public Connection getConnection(String tenantIdentifier) throws SQLException {
protected Connection getConnection(String tenantIdentifier, Connection connection) throws SQLException {
String schema = convertTenantIdToSchemaName(tenantIdentifier);
logger.debug("Setting schema to {}", schema);

try (Statement sqlStatement = connection.createStatement()) {
sqlStatement.execute(String.format("SET SCHEMA '%s';", schema));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public Optional<String> getJwkSetUri(String tenantId) {
return getTenantConfigById(tenantId).map(TenantConfig::jwkSetUri);
}

public Set<String> getAllTenantIds() {
return this.tenantConfigs.keySet();
}

public record TenantConfig(String tenantId, String[] okrChampionEmails, String jwkSetUri, String issuerUrl,
String clientId, DataSourceConfig dataSourceConfig) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import static ch.puzzle.okr.Constants.BACK_LOG_QUARTER_LABEL;

import ch.puzzle.okr.models.Quarter;
import ch.puzzle.okr.multitenancy.TenantConfigProvider;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.service.persistence.QuarterPersistenceService;
import ch.puzzle.okr.service.validation.QuarterValidationService;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,6 +26,7 @@ public class QuarterBusinessService {

private final QuarterPersistenceService quarterPersistenceService;
private final QuarterValidationService validator;
private final TenantConfigProvider tenantConfigProvider;

@Value("${okr.quarter.business.year.start}")
private int quarterStart;
Expand All @@ -31,9 +35,10 @@ public class QuarterBusinessService {
private String quarterFormat;

public QuarterBusinessService(QuarterPersistenceService quarterPersistenceService,
QuarterValidationService validator) {
QuarterValidationService validator, TenantConfigProvider tenantConfigProvider) {
this.quarterPersistenceService = quarterPersistenceService;
this.validator = validator;
this.tenantConfigProvider = tenantConfigProvider;
}

public Quarter getQuarterById(Long quarterId) {
Expand Down Expand Up @@ -78,12 +83,14 @@ private int getStartOfBusinessYear(YearMonth startOfQuarter, int quarter) {
return startOfQuarter.minusMonths((quarter - 1) * 3L).getYear();
}

private void generateQuarter(LocalDateTime start, String label) {
private void generateQuarter(LocalDate start, String label, String schema) {
TenantContext.setCurrentTenant(schema);

YearMonth yearMonth = YearMonth.from(start);
Quarter quarter = Quarter.Builder
.builder()
.withLabel(label)
.withStartDate(start.toLocalDate())
.withStartDate(start)
.withEndDate(yearMonth.plusMonths(2).atEndOfMonth())
.build();
validator.validateOnGeneration(quarter);
Expand All @@ -97,7 +104,7 @@ private boolean isInLastMonthOfQuarter(int currentQuarter, int nextQuarter) {
return Math.abs(nextQuarter - currentQuarter) == 2;
}

YearMonth getCurrentYearMonth() {
public YearMonth getCurrentYearMonth() {
return YearMonth.now();
}

Expand All @@ -121,11 +128,18 @@ public void scheduledGenerationQuarters() {
int currentQuarter = quarters.get(currentYearMonth.getMonthValue());
int nextQuarter = quarters.get(nextQuarterYearMonth.getMonthValue());

String initialTenant = TenantContext.getCurrentTenant();

Set<String> tenantSchemas = this.tenantConfigProvider.getAllTenantIds();
// If we are in the last month of a quarter, generate the next quarter
if (isInLastMonthOfQuarter(currentQuarter, nextQuarter)) {
logger.info("Generated quarters on last day of month");
String label = createQuarterLabel(nextQuarterYearMonth, nextQuarter);
generateQuarter(nextQuarterYearMonth.atDay(1).atStartOfDay(), label);
for (String schema : tenantSchemas) {
logger.info("Generated quarters on last day of month for tenant {}", schema);
String label = createQuarterLabel(nextQuarterYearMonth, nextQuarter);
generateQuarter(nextQuarterYearMonth.atDay(1), label, schema);
}
}

TenantContext.setCurrentTenant(initialTenant);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
import static org.mockito.Mockito.*;

import ch.puzzle.okr.models.Quarter;
import ch.puzzle.okr.multitenancy.TenantConfigProvider;
import ch.puzzle.okr.service.persistence.QuarterPersistenceService;
import ch.puzzle.okr.service.validation.QuarterValidationService;
import ch.puzzle.okr.test.TestHelper;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -35,6 +34,9 @@ class QuarterBusinessServiceTest {
@Mock
QuarterPersistenceService quarterPersistenceService;

@Mock
TenantConfigProvider tenantConfigProvider;

@Mock
QuarterValidationService quarterValidationService;

Expand Down Expand Up @@ -119,6 +121,7 @@ void shouldNotGenerateQuarterIfNotLastMonthOfQuarter(int month) {
@ValueSource(ints = { 3, 6, 9, 12 })
void shouldGenerateQuarterIfLastMonthOfQuarter(int month) {
ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", 7);
Mockito.doReturn(Set.of(TestHelper.SCHEMA_PITC)).when(tenantConfigProvider).getAllTenantIds();

Mockito.when(quarterBusinessService.getCurrentYearMonth()).thenReturn(YearMonth.of(2030, month));
quarterBusinessService.scheduledGenerationQuarters();
Expand All @@ -139,6 +142,8 @@ private static Stream<Arguments> generateQuarterParams() {
@MethodSource("generateQuarterParams")
void shouldGenerateCorrectQuarter(int quarterStart, String quarterFormat, YearMonth currentYearMonth,
String expectedLabel) {
Mockito.doReturn(Set.of(TestHelper.SCHEMA_PITC)).when(tenantConfigProvider).getAllTenantIds();

ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", quarterStart);
ReflectionTestUtils.setField(quarterBusinessService, "quarterFormat", quarterFormat);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,48 @@
import static org.junit.jupiter.api.Assertions.*;

import ch.puzzle.okr.models.Quarter;
import ch.puzzle.okr.multitenancy.TenantConfigProvider;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.service.business.QuarterBusinessService;
import ch.puzzle.okr.test.SpringIntegrationTest;
import ch.puzzle.okr.test.TestHelper;
import ch.puzzle.okr.util.quarter.check.QuarterRangeChecker;
import java.time.LocalDate;
import java.time.Year;
import java.time.YearMonth;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.springframework.test.util.ReflectionTestUtils;

@SpringIntegrationTest
class QuarterPersistenceServiceIT {

private static final Logger log = LoggerFactory.getLogger(QuarterPersistenceServiceIT.class);
@MockitoSpyBean
@Autowired
private QuarterPersistenceService quarterPersistenceService;

@MockitoSpyBean
private TenantConfigProvider tenantConfigProvider;

@MockitoSpyBean
@Autowired
@InjectMocks
private QuarterBusinessService quarterBusinessService;

@BeforeEach
void setUp() {
TenantContext.setCurrentTenant(TestHelper.SCHEMA_PITC);
Expand Down Expand Up @@ -113,4 +137,31 @@ void findByLabelShouldReturnNullWhenLabelIsNull() {
void getModelNameShouldReturnQuarter() {
assertEquals(QUARTER, quarterPersistenceService.getModelName());
}

@ParameterizedTest(name = "Should generate quarter with Cron-Job when current month is the last month of the current quarter (Month: {0}, Quarter: {1})")
@CsvSource(value = { "1,1,0", "2,1,0", "3,1,1", "4,1,0", "5,1,0", "6,2,1", "7,1,0", "8,1,0", "9,3,1", "10,3,0",
"11,1,0", "12,4,1" })
void shouldGenerateQuarterWithCronJob(int month, int quarterIndex, int amountOfInvocations) {
int startQuarter = 7;
ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", startQuarter);
int nextYear = Year.now().atMonth(startQuarter).plusMonths(month + 12 - 1).getYear();
int nextYearShort = nextYear % 1000;
String expectedLabel = "GJ " + nextYearShort + "/" + (nextYearShort + 1) + "-Q" + quarterIndex;

Mockito.doReturn(YearMonth.of(nextYear, month)).when(quarterBusinessService).getCurrentYearMonth();
Mockito.doReturn(Set.of(TestHelper.SCHEMA_PITC)).when(tenantConfigProvider).getAllTenantIds();

quarterBusinessService.scheduledGenerationQuarters();

Mockito.verify(quarterPersistenceService, Mockito.times(amountOfInvocations)).save(ArgumentMatchers.any());

List<Quarter> createdQuarters = quarterPersistenceService
.findAll()
.stream()
.filter(quarter -> quarter.getLabel().equals(expectedLabel))
.toList();
assertEquals(amountOfInvocations, createdQuarters.size());
assertEquals(4 + amountOfInvocations, quarterBusinessService.getQuarters().size());
createdQuarters.forEach(quarter -> quarterPersistenceService.deleteById(quarter.getId()));
}
}
Loading