Skip to content
Open
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
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10

- package-ecosystem: gradle
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up JDK 21
uses: actions/setup-java@v5
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: temurin
java-version: '21'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6

- name: Build
run: |
Expand All @@ -45,7 +45,7 @@ jobs:
-exec cp {} jars/ \;

- name: Upload JARs
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: jars-${{ github.sha }}
path: jars/*.jar
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CodeQL

on:
push:
branches: [main, master, develop]
pull_request:
schedule:
- cron: '23 7 * * 1'
workflow_dispatch:

permissions:
actions: read
contents: read
security-events: write

jobs:
analyze:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [java-kotlin]

steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: temurin
java-version: '21'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6

- name: Initialize CodeQL
uses: github/codeql-action/init@cb06a0a8527b2c6970741b3a0baa15231dc74a4c # v4.34.1
with:
languages: ${{ matrix.language }}

- name: Make Gradle executable
run: chmod +x ./gradlew

- name: Autobuild
uses: github/codeql-action/autobuild@cb06a0a8527b2c6970741b3a0baa15231dc74a4c # v4.34.1

- name: Analyze
uses: github/codeql-action/analyze@cb06a0a8527b2c6970741b3a0baa15231dc74a4c # v4.34.1
with:
category: /language:${{ matrix.language }}
15 changes: 15 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Dependency Review

on:
pull_request:

permissions:
contents: read
pull-requests: write

jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Dependency Review
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ void truncateTables() throws SQLException {
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE AgentHistory;
TRUNCATE TABLE FreeholdHistory;
TRUNCATE TABLE LeaseHistory;
TRUNCATE TABLE LeaseholdHistory;
TRUNCATE TABLE FreeholdContractAgentInvite;
TRUNCATE TABLE FreeholdContractBidPayment;
TRUNCATE TABLE FreeholdContractBid;
TRUNCATE TABLE FreeholdContractOfferPayment;
TRUNCATE TABLE FreeholdContractOffer;
TRUNCATE TABLE FreeholdContractSanctionedAuctioneers;
TRUNCATE TABLE FreeholdContractAuction;
TRUNCATE TABLE LeaseContract;
TRUNCATE TABLE LeaseholdContract;
TRUNCATE TABLE FreeholdContract;
TRUNCATE TABLE Contract;
TRUNCATE TABLE RealtyRegion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.github.md5sha256.realty.database.RealtyLogicImpl.AcceptAgentInviteResult;
import io.github.md5sha256.realty.database.RealtyLogicImpl.InviteAgentResult;
import io.github.md5sha256.realty.database.RealtyLogicImpl.RemoveSanctionedAuctioneerResult;
import io.github.md5sha256.realty.database.RealtyLogicImpl.RejectAgentInviteResult;
import io.github.md5sha256.realty.database.RealtyLogicImpl.WithdrawAgentInviteResult;
import org.apache.ibatis.session.SqlSession;
Expand Down Expand Up @@ -202,7 +203,7 @@ void succeeds() {
createFreeholdRegion(regionId);
logic.inviteAgent(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);

WithdrawAgentInviteResult result = logic.withdrawAgentInvite(regionId, WORLD_ID, PLAYER_A);
WithdrawAgentInviteResult result = logic.withdrawAgentInvite(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);
Assertions.assertInstanceOf(WithdrawAgentInviteResult.Success.class, result);
}

Expand All @@ -212,21 +213,42 @@ void notFound() {
String regionId = uniqueRegionId();
createFreeholdRegion(regionId);

WithdrawAgentInviteResult result = logic.withdrawAgentInvite(regionId, WORLD_ID, PLAYER_A);
WithdrawAgentInviteResult result = logic.withdrawAgentInvite(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);
Assertions.assertInstanceOf(WithdrawAgentInviteResult.NotFound.class, result);
}

@Test
@DisplayName("returns NoFreeholdContract when region has no freehold")
void noFreeholdContract() {
WithdrawAgentInviteResult result = logic.withdrawAgentInvite("missing", WORLD_ID, TITLE_HOLDER, PLAYER_A);
Assertions.assertInstanceOf(WithdrawAgentInviteResult.NoFreeholdContract.class, result);
}

@Test
@DisplayName("withdrawing prevents future acceptance")
void preventsAcceptance() {
String regionId = uniqueRegionId();
createFreeholdRegion(regionId);
logic.inviteAgent(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);
logic.withdrawAgentInvite(regionId, WORLD_ID, PLAYER_A);
logic.withdrawAgentInvite(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);

AcceptAgentInviteResult result = logic.acceptAgentInvite(regionId, WORLD_ID, PLAYER_A);
Assertions.assertInstanceOf(AcceptAgentInviteResult.NotFound.class, result);
}

@Test
@DisplayName("returns NotTitleHolder when caller is not the title holder")
void notTitleHolder() {
String regionId = uniqueRegionId();
createFreeholdRegion(regionId);
logic.inviteAgent(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);

WithdrawAgentInviteResult result = logic.withdrawAgentInvite(regionId, WORLD_ID, PLAYER_B, PLAYER_A);
Assertions.assertInstanceOf(WithdrawAgentInviteResult.NotTitleHolder.class, result);

AcceptAgentInviteResult stillPending = logic.acceptAgentInvite(regionId, WORLD_ID, PLAYER_A);
Assertions.assertInstanceOf(AcceptAgentInviteResult.Success.class, stillPending);
}
}

// --- Reject Agent Invite ---
Expand Down Expand Up @@ -284,18 +306,26 @@ void succeeds() {
createFreeholdRegion(regionId);
inviteAndAcceptAgent(regionId, PLAYER_A);

int rows = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertEquals(1, rows);
RemoveSanctionedAuctioneerResult result = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.Success.class, result);
}

@Test
@DisplayName("returns 0 when agent does not exist")
@DisplayName("returns NotFound when agent does not exist")
void notFound() {
String regionId = uniqueRegionId();
createFreeholdRegion(regionId);

int rows = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertEquals(0, rows);
RemoveSanctionedAuctioneerResult result = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.NotFound.class, result);
}

@Test
@DisplayName("returns NoFreeholdContract when region has no freehold")
void noFreeholdContract() {
RemoveSanctionedAuctioneerResult result = logic.removeSanctionedAuctioneer(
"missing", WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.NoFreeholdContract.class, result);
}

@Test
Expand All @@ -305,23 +335,38 @@ void canReInviteAfterRemoval() {
createFreeholdRegion(regionId);
inviteAndAcceptAgent(regionId, PLAYER_A);

logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
RemoveSanctionedAuctioneerResult removal = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.Success.class, removal);

InviteAgentResult result = logic.inviteAgent(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);
Assertions.assertInstanceOf(InviteAgentResult.Success.class, result);
}

@Test
@DisplayName("removing is idempotent - second removal returns 0")
@DisplayName("removing is idempotent - second removal returns NotFound")
void doubleRemoval() {
String regionId = uniqueRegionId();
createFreeholdRegion(regionId);
inviteAndAcceptAgent(regionId, PLAYER_A);

int first = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
int second = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertEquals(1, first);
Assertions.assertEquals(0, second);
RemoveSanctionedAuctioneerResult first = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
RemoveSanctionedAuctioneerResult second = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.Success.class, first);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.NotFound.class, second);
}

@Test
@DisplayName("returns NotTitleHolder when caller is not the title holder")
void notTitleHolder() {
String regionId = uniqueRegionId();
createFreeholdRegion(regionId);
inviteAndAcceptAgent(regionId, PLAYER_A);

RemoveSanctionedAuctioneerResult result = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, PLAYER_B);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.NotTitleHolder.class, result);

InviteAgentResult stillAgent = logic.inviteAgent(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);
Assertions.assertInstanceOf(InviteAgentResult.AlreadyAgent.class, stillAgent);
}
}

Expand Down Expand Up @@ -350,8 +395,8 @@ void fullCycle() {
Assertions.assertInstanceOf(InviteAgentResult.AlreadyAgent.class, duplicate);

// Remove
int rows = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertEquals(1, rows);
RemoveSanctionedAuctioneerResult removal = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.Success.class, removal);

// Can invite again after removal
InviteAgentResult reInvite = logic.inviteAgent(regionId, WORLD_ID, TITLE_HOLDER, PLAYER_A);
Expand All @@ -368,10 +413,10 @@ void multipleAgents() {
inviteAndAcceptAgent(regionId, PLAYER_B);

// Both should be removable independently
int rowsA = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
int rowsB = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_B, TITLE_HOLDER);
Assertions.assertEquals(1, rowsA);
Assertions.assertEquals(1, rowsB);
RemoveSanctionedAuctioneerResult rowsA = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_A, TITLE_HOLDER);
RemoveSanctionedAuctioneerResult rowsB = logic.removeSanctionedAuctioneer(regionId, WORLD_ID, PLAYER_B, TITLE_HOLDER);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.Success.class, rowsA);
Assertions.assertInstanceOf(RemoveSanctionedAuctioneerResult.Success.class, rowsB);
}
}
}
Loading