Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add concurrency tests
  • Loading branch information
meedbek committed May 22, 2025
commit f4470f384d4a9abed862fc26dd9c1fe5990e0223
2 changes: 1 addition & 1 deletion java/jdbc/SessionlessTransactions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<name>SessionlessTransactionsDemo</name>
<description>Sessionless transactions demo</description>
<properties>
<java.version>17</java.version>
<java.version>21</java.version>
<ojdbc.version>23.8.0.25.04</ojdbc.version>
</properties>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsResponse;
import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionResponse;
import com.oracle.jdbc.samples.sessionlesstxns.service.BookingService;
import com.oracle.jdbc.samples.sessionlesstxns.service.PaymentService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,16 @@

package com.oracle.jdbc.samples.sessionlesstxns;

import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutResponse;
import com.oracle.jdbc.samples.sessionlesstxns.dto.RemoveTicketRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsResponse;
import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionResponse;
import com.oracle.jdbc.samples.sessionlesstxns.exception.PaymentFailedException;
import com.oracle.jdbc.samples.sessionlesstxns.service.PaymentService;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.*;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.List;
import java.util.Scanner;

@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestApis {
public class TestApis extends TestBase {

static final long FLIGHT1_ID = 0;
static final int FLIGHT1_AVAILABLE_SEATS = 1;
Expand All @@ -48,36 +26,12 @@ public class TestApis {
static final String DUMMY_RECEIPT_NUMBER = "1442AZ";
static final int DEFAULT_TIMEOUT = 1;

static {
RestAssured.baseURI = "http://127.0.0.1";
}

@LocalServerPort
private int port;

@BeforeAll
void setUp() {
RestAssured.port = port;
createSchema();
}

@AfterAll
void tearDown() {
dropSchema();
}

@BeforeEach
void updateData() {
cleanTables();
loadData();
}

@Autowired
protected JdbcTemplate testJdbcTemplate;

@Autowired
protected DataSource dataSource;

static protected PaymentService paymentService = Mockito.mock(PaymentService.class);

@TestConfiguration
Expand Down Expand Up @@ -128,7 +82,7 @@ void testNormalScenario() {
Assertions.assertEquals(FLIGHT1_REQUESTED_SEATS + FLIGHT2_REQUESTED_SEATS, checkout.tickets().size());

final String queryBookingOrder = "SELECT id FROM bookings WHERE id = ?";
Assertions.assertNotNull(testJdbcTemplate.queryForObject(queryBookingOrder, Long.class, startTransaction.bookingId()));
Assertions.assertNotNull(jdbcTemplate.queryForObject(queryBookingOrder, Long.class, startTransaction.bookingId()));
}

/**
Expand Down Expand Up @@ -189,115 +143,11 @@ void thirdScenario() {
Assertions.assertTrue(checkout.tickets().stream().noneMatch(ticket -> ticket.seatId().equals(SEAT_ID_TO_CANCEL)));
}

private void createSchema() {
runSQLScript("createSchema.sql");
}

private void loadData() {
runSQLScript("dataLoader.sql");
}

private void dropSchema() {
runSQLScript("dropSchema.sql");
}

private void cleanTables() {
runSQLScript("dataCleaner.sql");
}

private void runSQLScript(String fileName) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
List<String> instructions = new Scanner(inputStream).useDelimiter(";").tokens().toList();
for (String sql : instructions) {
testJdbcTemplate.update(sql);
}
}

private StartTransactionResponse testAPIStartTransaction(int timeout, long flightId, int count, HttpStatus expectedStatus) {
var request = RestAssured.given()
.contentType(ContentType.JSON)
.body(new StartTransactionRequest(timeout, flightId, count))
.when()
.post("/api/v1/bookings");

if (expectedStatus.equals(HttpStatus.CREATED) || expectedStatus.equals(HttpStatus.PARTIAL_CONTENT)) {
return request
.then()
.statusCode(expectedStatus.value())
.and()
.extract()
.response()
.body().as(StartTransactionResponse.class);
}

request.then().statusCode(expectedStatus.value());
return null;
}

private RequestTicketsResponse testAPIRequestTickets(
String transactionId, long flightId, int count, long bookingId, HttpStatus expectedStatus) {
var request = RestAssured.given()
.contentType(ContentType.JSON)
.body(new RequestTicketsRequest(transactionId, flightId, count))
.pathParam("bookingId", bookingId)
.when()
.post("/api/v1/bookings/{bookingId}");


if (expectedStatus.equals(HttpStatus.CREATED) || expectedStatus.equals(HttpStatus.PARTIAL_CONTENT)) {
return request
.then()
.statusCode(expectedStatus.value())
.and()
.extract()
.response()
.body().as(RequestTicketsResponse.class);
}

request.then().statusCode(expectedStatus.value());
return null;
}

private void testAPIRemoveTicket(String transactionId, long bookingId, long seatId, HttpStatus expectedStatus) {
RestAssured.given()
.contentType(ContentType.JSON)
.body(new RemoveTicketRequest(transactionId, seatId))
.pathParams("bookingId", bookingId)
.when()
.delete("/api/v1/bookings/{bookingId}")
.then()
.statusCode(expectedStatus.value());
}

private CheckoutResponse testAPICheckout(
String transactionId, long paymentMethodId, long bookingId, HttpStatus expectedStatus) {
var request = RestAssured.given()
.contentType(ContentType.JSON)
.body(new CheckoutRequest(transactionId, paymentMethodId))
.pathParam("bookingId", bookingId)
.when()
.post("/api/v1/bookings/{bookingId}/checkout");

if (expectedStatus.equals(HttpStatus.CREATED)) {
return request
.then()
.statusCode(HttpStatus.CREATED.value())
.and()
.extract()
.response()
.body().as(CheckoutResponse.class);
}

request.then().statusCode(expectedStatus.value());
return null;
}

private void testAPICancelBooking(String transactionId, HttpStatus expectedStatus) {
RestAssured.given()
.contentType(ContentType.JSON)
.pathParam("transactionId", transactionId)
.when()
.post("/api/v1/bookings/cancel/{transactionId}")
.then().statusCode(expectedStatus.value());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright (c) 2025 Oracle, Inc.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/

package com.oracle.jdbc.samples.sessionlesstxns;

import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutResponse;
import com.oracle.jdbc.samples.sessionlesstxns.dto.RemoveTicketRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsResponse;
import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionRequest;
import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionResponse;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;

import java.io.InputStream;
import java.util.List;
import java.util.Scanner;

@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestBase {
static {
RestAssured.baseURI = "http://127.0.0.1";
}

@LocalServerPort
private int port;


@Autowired
protected JdbcTemplate jdbcTemplate;

@BeforeAll
void setUp() {
RestAssured.port = port;
createSchema();
}

@AfterAll
void tearDown() {
dropSchema();
}

public void runSQLScript(String fileName) {
InputStream inputStream = TestBase.class.getClassLoader().getResourceAsStream(fileName);
List<String> instructions = new Scanner(inputStream).useDelimiter(";").tokens().toList();
jdbcTemplate.batchUpdate(instructions.toArray(new String[0]));
}

public static StartTransactionResponse testAPIStartTransaction(int timeout, long flightId, int count, HttpStatus expectedStatus) {
var request = RestAssured.given()
.contentType(ContentType.JSON)
.body(new StartTransactionRequest(timeout, flightId, count))
.when()
.post("/api/v1/bookings");

if (expectedStatus.equals(HttpStatus.CREATED) || expectedStatus.equals(HttpStatus.PARTIAL_CONTENT)) {
return request
.then()
.statusCode(expectedStatus.value())
.and()
.extract()
.response()
.body().as(StartTransactionResponse.class);
}

request.then().statusCode(expectedStatus.value());
return null;
}

public static RequestTicketsResponse testAPIRequestTickets(
String transactionId, long flightId, int count, long bookingId, HttpStatus expectedStatus) {
var request = RestAssured.given()
.contentType(ContentType.JSON)
.body(new RequestTicketsRequest(transactionId, flightId, count))
.pathParam("bookingId", bookingId)
.when()
.post("/api/v1/bookings/{bookingId}");


if (expectedStatus.equals(HttpStatus.CREATED) || expectedStatus.equals(HttpStatus.PARTIAL_CONTENT)) {
return request
.then()
.statusCode(expectedStatus.value())
.and()
.extract()
.response()
.body().as(RequestTicketsResponse.class);
}

request.then().statusCode(expectedStatus.value());
return null;
}

public void testAPIRemoveTicket(String transactionId, long bookingId, long seatId, HttpStatus expectedStatus) {
RestAssured.given()
.contentType(ContentType.JSON)
.body(new RemoveTicketRequest(transactionId, seatId))
.pathParams("bookingId", bookingId)
.when()
.delete("/api/v1/bookings/{bookingId}")
.then()
.statusCode(expectedStatus.value());
}

public static CheckoutResponse testAPICheckout(
String transactionId, long paymentMethodId, long bookingId, HttpStatus expectedStatus) {
var request = RestAssured.given()
.contentType(ContentType.JSON)
.body(new CheckoutRequest(transactionId, paymentMethodId))
.pathParam("bookingId", bookingId)
.when()
.post("/api/v1/bookings/{bookingId}/checkout");

if (expectedStatus.equals(HttpStatus.CREATED)) {
return request
.then()
.statusCode(HttpStatus.CREATED.value())
.and()
.extract()
.response()
.body().as(CheckoutResponse.class);
}

request.then().statusCode(expectedStatus.value());
return null;
}

public void testAPICancelBooking(String transactionId, HttpStatus expectedStatus) {
RestAssured.given()
.contentType(ContentType.JSON)
.pathParam("transactionId", transactionId)
.when()
.post("/api/v1/bookings/cancel/{transactionId}")
.then().statusCode(expectedStatus.value());
}

private void createSchema() {
runSQLScript("createSchema.sql");
}

private void dropSchema() {
runSQLScript("dropSchema.sql");
}
}
Loading