Compare commits

..

No commits in common. "cbf68f8657fa0647057a649dec4fd38a032510d5" and "e8b74ad1c0faf17aa6b74422e296da5391252f11" have entirely different histories.

13 changed files with 67 additions and 157 deletions

View File

@ -57,7 +57,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
@Override
@Transactional
public ResponseEntity<HsOfficeCoopSharesTransactionResource> postCoopSharesTransaction(
public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction(
final String currentSubject,
final String assumedRoles,
final HsOfficeCoopSharesTransactionInsertResource requestBody) {
@ -131,9 +131,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
}
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if ( resource.getRevertedShareTxUuid() != null ) {
entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid()))));
if ( resource.getAdjustedShareTxUuid() != null ) {
entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getAdjustedShareTxUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getAdjustedShareTxUuid()))));
}
};
}

View File

@ -14,7 +14,6 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
HsOfficeMembershipEntity save(final HsOfficeMembershipEntity entity);
List<HsOfficeMembershipEntity> findAll();
@Query("""
SELECT membership FROM HsOfficeMembershipEntity membership
@ -23,7 +22,6 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix
""")
List<HsOfficeMembershipEntity> findMembershipsByOptionalPartnerUuid(UUID partnerUuid);
@Query("""
SELECT membership FROM HsOfficeMembershipEntity membership
WHERE (:partnerNumber = membership.partner.partnerNumber)
@ -33,18 +31,10 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
HsOfficeMembershipEntity findMembershipByPartnerNumberAndSuffix(
@NotNull Integer partnerNumber,
@NotNull String suffix);
default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) {
final var partnerNumber = memberNumber / 100;
final String suffix = String.format("%02d", memberNumber % 100);
final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix);
// FIXME: remove
if (result == null) {
final var all = findAll();
all.size();
}
return result;
final var suffix = memberNumber % 100;
return findMembershipByPartnerNumberAndSuffix(partnerNumber, String.format("%02d", suffix));
}
long count();

View File

@ -6,7 +6,7 @@ components:
HsOfficeCoopAssetsTransactionType:
type: string
enum:
- ADJUSTMENT # FIXME: rename to REVERSAL
- ADJUSTMENT
- DEPOSIT
- DISBURSAL
- TRANSFER
@ -32,9 +32,9 @@ components:
type: string
comment:
type: string
adjustedAssetTx: # FIXME: rename to revertedAssetTx
adjustedAssetTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
adjustmentAssetTx: # FIXME: rename to reversalAssetTx
adjustmentAssetTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
HsOfficeReferencedCoopAssetsTransaction:
@ -80,7 +80,7 @@ components:
maxLength: 48
comment:
type: string
reverseEntry.uuid: # FIXME: rename to revertedAssetTx
reverseEntry.uuid:
type: string
format: uuid
required:

View File

@ -6,7 +6,7 @@ components:
HsOfficeCoopSharesTransactionType:
type: string
enum:
- ADJUSTMENT # FIXME: rename to REVERSAL
- ADJUSTMENT
- SUBSCRIPTION
- CANCELLATION
@ -27,9 +27,9 @@ components:
type: string
comment:
type: string
revertedShareTx:
adjustedShareTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
reversalShareTx:
adjustmentShareTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
HsOfficeReferencedCoopSharesTransaction:
@ -73,7 +73,7 @@ components:
maxLength: 48
comment:
type: string
revertedShareTx.uuid:
adjustedShareTx.uuid:
type: string
format: uuid
required:

View File

@ -46,7 +46,7 @@ post:
summary: Adds a new cooperative share transaction.
tags:
- hs-office-coopShares
operationId: postCoopSharesTransaction
operationId: addCoopSharesTransaction
parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -28,9 +28,6 @@ create table if not exists hs_office.relation
);
--//
-- TODO.impl: unique constraint, to prevent using the same person multiple times as a partner, or better:
-- ( anchorUuid, holderUuid, type)
-- ============================================================================
--changeset michael.hoennig:hs-office-relation-MAIN-TABLE-JOURNAL endDelimiter:--//

View File

@ -12,9 +12,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.debitor.FinallyDeleteSepaMa
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDebitor;
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CancelMembership;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateCoopSharesCancellationTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateCoopSharesRevertTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateCoopSharesSubscriptionTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CoopSharesTransactionUseCase;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership;
import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner;
import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner;
@ -264,18 +262,18 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Test
@Order(2020)
@Requires("Debitor: D-3101000 - Test AG - main debitor")
@Requires("Debitor: Test AG - main debitor")
@Disabled("see TODO.spec in DontDeleteDefaultDebitor")
void shouldNotDeleteDefaultDebitor() {
new DontDeleteDefaultDebitor(this)
.given("partnerNumber", 31010)
.given("partnerNumber", 31020)
.given("debitorSuffix", "00")
.doRun();
}
@Test
@Order(3100)
@Requires("Debitor: D-3101000 - Test AG - main debitor")
@Requires("Debitor: Test AG - main debitor")
@Produces("SEPA-Mandate: Test AG")
void shouldCreateSepaMandateForDebitor() {
new CreateSepaMandateForDebitor(this)
@ -317,7 +315,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Test
@Order(4000)
@Requires("Partner: P-31010 - Test AG")
@Produces("Membership: M-3101000 - Test AG")
@Produces("Membership: Test AG 00")
void shouldCreateMembershipForPartner() {
new CreateMembership(this)
.given("partnerName", "Test AG")
@ -330,14 +328,15 @@ class HsOfficeScenarioTests extends ScenarioTest {
}
@Test
@Order(4201)
@Requires("Membership: M-3101000 - Test AG")
@Order(4200)
@Requires("Membership: Test AG 00")
@Produces("Coop-Shares SUBSCRIPTION Transaction")
void shouldSubscribeCoopShares() {
new CreateCoopSharesSubscriptionTransaction(this)
void testCoopSharesSubscriptionTransaction() {
new CoopSharesTransactionUseCase(this)
.given("memberNumber", "3101000")
.given("transactionType", "SUBSCRIPTION")
.given("reference", "sign 2024-01-15")
.given("shareCount", 100)
.given("shareCount", "100")
.given("comment", "Signing the Membership")
.given("transactionDate", "2024-01-15")
.doRun();
@ -345,12 +344,16 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Test
@Order(4201)
@Requires("Membership: M-3101000 - Test AG")
void shouldRevertCoopSharesSubscription() {
new CreateCoopSharesRevertTransaction(this)
.given("memberNumber", "3101000")
.given("comment", "reverting some incorrect subscription")
.given("dateOfIncorrectTransaction", "2024-02-15")
@Requires("Coop-Shares SUBSCRIPTION Transaction")
@Produces("Coop-Shares ADJUSTMENT Transaction")
void testCoopSharesAdjustmentTransaction() {
new CoopSharesTransactionUseCase(this)
.given("memberNumber", "3102000")
.given("transactionType", "ADJUSTMENT")
.given("reference", "adjust 2024-01-16")
.given("shareCount", "-90")
.given("comment", "Cancelling 90 Shares, correcting wrong number of digits in subscription")
.given("transactionDate", "2024-01-16")
.doRun();
}
@ -358,11 +361,12 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Order(4202)
@Requires("Coop-Shares SUBSCRIPTION Transaction")
@Produces("Coop-Shares CANCELLATION Transaction")
void shouldCancelCoopSharesSubscription() {
new CreateCoopSharesCancellationTransaction(this)
.given("memberNumber", "3101000")
void testCoopSharesCancellationTransaction() {
new CoopSharesTransactionUseCase(this)
.given("memberNumber", "3102000")
.given("transactionType", "CANCELLATION")
.given("reference", "cancel 2024-01-15")
.given("sharesToCancel", 8)
.given("shareCount", "8")
.given("comment", "Cancelling 8 Shares")
.given("transactionDate", "2024-02-15")
.doRun();
@ -370,7 +374,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Test
@Order(4900)
@Requires("Membership: M-3101000 - Test AG")
@Requires("Membership: Test AG 00")
void shouldCancelMembershipOfPartner() {
new CancelMembership(this)
.given("memberNumber", "3101000")

View File

@ -1,7 +1,6 @@
package net.hostsharing.hsadminng.hs.office.scenarios;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.TestInfo;
@ -16,27 +15,23 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestReport {
private final static File markdownLogFile = new File("doc/scenarios/last-debug-log.md");
private final Map<String, ?> aliases;
private final PrintWriter markdownLog; // records everything for debugging purposes
private File markdownReportFile;
private PrintWriter markdownReport; // records only the use-case under test, without its pre-requisites
private final StringBuilder markdownLog = new StringBuilder(); // records everything for debugging purposes
private PrintWriter markdownReport;
private int silent; // do not print anything to test-report if >0
@SneakyThrows
public TestReport(final Map<String, ?> aliases) {
this.aliases = aliases;
this.markdownLog = new PrintWriter(new FileWriter(markdownLogFile));
}
public void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException {
final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow();
final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow();
assertThat(new File("doc/scenarios/").isDirectory() || new File("doc/scenarios/").mkdirs()).as("mkdir doc/scenarios/").isTrue();
markdownReportFile = new File("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md");
markdownReport = new PrintWriter(new FileWriter(markdownReportFile));
print("## Scenario #" + determineScenarioTitle(testInfo));
markdownReport = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md"));
print("## Scenario #" + testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " +
testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2"));
}
@SneakyThrows
@ -50,7 +45,7 @@ public class TestReport {
}
// but the debugLog should contain all output, even if silent
markdownLog.print(outputWithCommentsForUuids);
markdownLog.append(outputWithCommentsForUuids);
}
public void printLine(final String output) {
@ -64,21 +59,7 @@ public class TestReport {
public void close() {
if (markdownReport != null) {
markdownReport.close();
System.out.println("SCENARIO REPORT: " + asClickableLink(markdownReportFile));
}
markdownLog.close();
System.out.println("DEBUG LOG: " + asClickableLink(markdownLogFile));
}
private static @NotNull String determineScenarioTitle(final TestInfo testInfo) {
final var convertedTestMethodName =
testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " +
testInfo.getTestMethod().map(Method::getName).map(t -> t.replaceAll("([a-z])([A-Z]+)", "$1 $2")).orElseThrow();
return convertedTestMethodName.replaceAll(": should ", ": ");
}
private String asClickableLink(final File file) {
return file.toURI().toString().replace("file:/", "file:///");
}
private static Object orderNumber(final Method method) {
@ -107,4 +88,5 @@ public class TestReport {
code.run();
silent--;
}
}

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.scenarios;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import io.restassured.http.ContentType;
import lombok.Getter;
import lombok.SneakyThrows;
@ -82,7 +81,7 @@ public abstract class UseCase<T extends UseCase<?>> {
testReport.silent(() ->
requirements.forEach((alias, factory) -> {
if (!ScenarioTest.containsAlias(alias)) {
factory.apply(alias).run().keepAs(alias);
factory.apply(alias).run().keep();
}
})
);
@ -127,7 +126,7 @@ public abstract class UseCase<T extends UseCase<?>> {
}
public HttpResponse withTitle(final String title, final Supplier<HttpResponse> code) {
this.nextTitle = ScenarioTest.resolve(title);
this.nextTitle = title;
final var response = code.get();
this.nextTitle = null;
return response;
@ -277,18 +276,13 @@ public abstract class UseCase<T extends UseCase<?>> {
return this;
}
public HttpResponse keepAs(final String alias) {
ScenarioTest.putAlias(
alias == null ? "unknown alias" : alias, // FIXME
new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid));
return this;
}
public HttpResponse keep() {
final var alias = nextTitle != null ? nextTitle : resultAlias;
// FIXME assertThat(alias).as("cannot keep result, no title or alias found for locationUuid: " + locationUuid).isNotNull();
return keepAs(alias);
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
ScenarioTest.putAlias(
alias,
new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid));
return this;
}
@SneakyThrows
@ -311,7 +305,8 @@ public abstract class UseCase<T extends UseCase<?>> {
public <T> Optional<T> getFromBodyAsOptional(final String path) {
try {
return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path)));
} catch (final PathNotFoundException e) {
} catch (final Exception e) {
// FIXME: catch more precise exception class
return null; // means the property did not exist at all, not that it was there with value null
}
}

View File

@ -8,9 +8,9 @@ import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.OK;
public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopSharesTransaction> {
public class CoopSharesTransactionUseCase extends UseCase<CoopSharesTransactionUseCase> {
public CreateCoopSharesTransaction(final ScenarioTest testSuite) {
public CoopSharesTransactionUseCase(final ScenarioTest testSuite) {
super(testSuite);
}
@ -23,20 +23,17 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
response -> response.getFromBody("$[0].uuid")
);
return withTitle("Create the CoopShares-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopsharestransactions", usingJsonBody("""
return httpPost("/api/hs/office/coopsharestransactions", usingJsonBody("""
{
"membership.uuid": ${membershipUuid},
"transactionType": ${transactionType},
"reference": ${reference},
"shareCount": ${shareCount},
"comment": ${comment},
"valueDate": ${transactionDate},
"revertedShareTx.uuid": ${revertedShareTx???}
"valueDate": ${transactionDate}
}
"""))
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
);
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON);
}
@Override
@ -44,7 +41,8 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
verify("Verify Coop-Shares %{transactionType}-Transaction",
() -> httpGet("/api/hs/office/coopsharestransactions/" + response.getLocationUuid())
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
path("transactionType").contains("%{transactionType}"),
// path("transactionType").contains("%{transactionType}"),
// path("memberNumber").contains("%{memberNumber}"),
path("shareCount").contains("%{shareCount}"),
path("comment").contains("%{comment}"),
path("valueDate").contains("%{transactionDate}")

View File

@ -1,17 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
public class CreateCoopSharesCancellationTransaction extends CreateCoopSharesTransaction {
public CreateCoopSharesCancellationTransaction(final ScenarioTest testSuite) {
super(testSuite);
}
@Override
protected HttpResponse run() {
given("transactionType", "CANCELLATION");
given("shareCount", "-%{sharesToCancel}");
return super.run();
}
}

View File

@ -1,27 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
public class CreateCoopSharesRevertTransaction extends CreateCoopSharesTransaction {
public CreateCoopSharesRevertTransaction(final ScenarioTest testSuite) {
super(testSuite);
requires("CoopShares-Transaction with incorrect shareCount", alias ->
new CreateCoopSharesSubscriptionTransaction(testSuite)
.given("memberNumber", "3101000")
.given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedShareTx
.given("shareCount", 100)
.given("comment", "reverting subscription transaction with wrong share count")
.given("transactionDate", "%{dateOfIncorrectTransaction}")
);
}
@Override
protected HttpResponse run() {
given("transactionType", "ADJUSTMENT");
given("shareCount", -100);
given("revertedShareTx", uuid("CoopShares-Transaction with incorrect shareCount"));
return super.run();
}
}

View File

@ -1,12 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
public class CreateCoopSharesSubscriptionTransaction extends CreateCoopSharesTransaction {
public CreateCoopSharesSubscriptionTransaction(final ScenarioTest testSuite) {
super(testSuite);
given("transactionType", "SUBSCRIPTION");
}
}