Erweitere VoTiAn NG um Auftrags-, Stamm- und Verwaltungsfunktionen
Ergänzt Auftragserfassung und -bearbeitung, Unternehmerbelege, Stornos, Lagerkorrekturen, Stammdaten, Abrechnung, CRM, Formulare, Berichte und Preisverwaltung auf Basis der aktuellen PHP-Anwendung. Enthält Integrations- und Oberflächentests sowie dokumentierte Abgleiche und Prüfstände.
This commit is contained in:
1 parent
42c87282ee
commit
363bc27c26
190 files changed
+14815
-1857
No files matched your search
@@ -0,0 +1,69 @@
|
||||
package de.stadtbote.votian;
|
||||
import de.stadtbote.votian.database.DatabaseService;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static de.stadtbote.votian.StatisticsService.*;
|
||||
import static de.stadtbote.votian.OrderEntryService.fields;
|
||||
class StatisticsServiceTest {
|
||||
final DatabaseService db=mock(DatabaseService.class);
|
||||
final StatisticsService service=new StatisticsService(db,new OrderEntryCatalog(db),"phoenix_log");
|
||||
final LegacyUser user=new LegacyUser(11,12,7,"test","1".repeat(70));
|
||||
static Query query(List<Long> hqs,List<Interval> comparisons,List<Split> splits,String number,LocalTime timeFrom,LocalTime timeTo){return new Query(new Interval(LocalDate.of(2026,1,1),LocalDate.of(2026,12,31)),comparisons,hqs,DateBasis.FINISHED,State.FINISHED,Amount.ALL,Payment.ALL,Period.MONTH,splits,number,"",null,null,true,null,null,timeFrom,timeTo,true,false);}
|
||||
@BeforeEach void setup(){
|
||||
when(db.readTransaction(any())).thenAnswer(c->((Supplier<?>)c.getArgument(0)).get());
|
||||
when(db.queryForList(anyString(),any(Object[].class))).thenAnswer(c->{String sql=c.getArgument(0);if(sql.contains("target.hq_id"))return List.of(fields("hq_id",7),fields("hq_id",8));return List.of(fields("orders",1234L,"revenue",12000));});
|
||||
}
|
||||
@Test void fullPeriodIsAggregatedWithoutTheOrderListLimit(){
|
||||
var q=query(List.of(7L),List.of(),List.of(Split.CUSTOMER,Split.HEADQUARTERS),"",null,null);
|
||||
var sql=StatisticsService.sql(q,q.interval(),List.of(7L),false,"`phoenix_log`.log");
|
||||
assertThat(sql.text()).contains("COUNT(*) AS orders","SUM(j.jb_totalprice)","GROUP BY period,split0,split1","LIMIT 5001").doesNotContain("LIMIT 500 ");
|
||||
assertThat(sql.values()).containsExactly(7L,LocalDateTime.of(2026,1,1,0,0),LocalDateTime.of(2027,1,1,0,0));
|
||||
var result=service.calculate(user,q);assertThat(result.get("completePeriod")).isEqualTo(true);
|
||||
assertThat(result.toString()).contains("1234");verify(db,never()).writeTransaction(any());
|
||||
}
|
||||
@Test void midnightTimeWindowAndTextAreBoundParameters(){
|
||||
String input="x' OR 1=1";var q=query(List.of(7L),List.of(),List.of(),input,LocalTime.of(22,0),LocalTime.of(5,0));
|
||||
var sql=StatisticsService.sql(q,q.interval(),List.of(7L),false,"`phoenix_log`.log");
|
||||
assertThat(sql.text()).contains("TIME(j.jb_finishtime)>=? OR TIME(j.jb_finishtime)<=?","cs.cs_eid=?").doesNotContain(input);
|
||||
assertThat(sql.values()).contains(input,LocalTime.of(22,0),LocalTime.of(5,0,59));
|
||||
}
|
||||
@Test void retrospectiveUsesLegacyHistoryAndAuxiliaryRowsDoNotMultiplyOrders(){
|
||||
var q=query(List.of(7L),List.of(),List.of(Split.START_POSTAL),"",null,null);
|
||||
var sql=StatisticsService.sql(q,q.interval(),List.of(7L),false,"`history`.log");
|
||||
assertThat(sql.text()).contains("EXISTS (SELECT 1 FROM `history`.log", "logo_id IN (1,25)","Nacherfassung%", "SUM(p.jbprc_price)").doesNotContain("JOIN jobprice", "JOIN tour ");
|
||||
assertThat(sql.text()).contains("COUNT(*)=1 THEN MAX(v.crvh_prov) ELSE NULL", "missingCourierValues");
|
||||
}
|
||||
@Test void foreignMandatorAndMissingMasterRightAreRejectedBeforeAggregation(){
|
||||
var q=query(List.of(9L),List.of(),List.of(),"",null,null);
|
||||
assertThatThrownBy(()->service.calculate(user,q)).isInstanceOf(AccessDeniedException.class);
|
||||
String rights="1".repeat(10)+"0"+"1".repeat(59);
|
||||
assertThatThrownBy(()->service.calculate(new LegacyUser(11,12,7,"test",rights),query(List.of(8L),List.of(),List.of(),"",null,null))).isInstanceOf(AccessDeniedException.class);
|
||||
verify(db,never()).queryForList(startsWith("SELECT 'Gesamt'"),any(Object[].class));
|
||||
}
|
||||
@Test void statisticsRightIsIndependentOfOrderReadRight(){
|
||||
assertThatThrownBy(()->service.calculate(new LegacyUser(11,12,7,"test","000010000000"),query(List.of(),List.of(),List.of(),"",null,null))).isInstanceOf(AccessDeniedException.class);
|
||||
verifyNoInteractions(db);
|
||||
var onlyStatistics=new LegacyUser(11,12,7,"stats","000000001000");
|
||||
assertThat(onlyStatistics.capabilities().readOrders()).isFalse();
|
||||
assertThat(onlyStatistics.capabilities().readStatistics()).isTrue();
|
||||
assertThat(service.calculate(onlyStatistics,query(List.of(7L),List.of(),List.of(),"",null,null))).containsEntry("completePeriod",true);
|
||||
}
|
||||
@Test void comparisonPeriodsAreIndependentlyBoundAndAllGetTotals(){
|
||||
var prior=new Interval(LocalDate.of(2025,1,1),LocalDate.of(2025,12,31));
|
||||
var result=service.calculate(user,query(List.of(7L),List.of(prior),List.of(),"",null,null));
|
||||
assertThat((List<?>)result.get("periods")).hasSize(2);
|
||||
verify(db,times(4)).queryForList(startsWith("SELECT "),any(Object[].class));
|
||||
}
|
||||
@Test void invalidRangeAndOversizedComparisonsAreRejected(){
|
||||
var q=query(List.of(7L),Collections.nCopies(6,new Interval(LocalDate.now(),LocalDate.now())),List.of(),"",null,null);
|
||||
assertThatThrownBy(()->service.calculate(user,q)).hasMessageContaining("fünf");verifyNoInteractions(db);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user