Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com> Reviewed-on: #51
This commit was merged in pull request #51.
This commit is contained in:
+33
-14
@@ -15,18 +15,40 @@ permissions:
|
|||||||
checks: write
|
checks: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install dependencies with Proxy
|
- name: Install JDK and Maven
|
||||||
env:
|
run: |
|
||||||
http_proxy: "http://host.docker.internal:3142"
|
# Kiểm tra proxy có hoạt động không
|
||||||
https_proxy: "http://host.docker.internal:3142"
|
echo "Acquire::http::Proxy \"$http_proxy\";" > /etc/apt/apt.conf.d/80proxy
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends --no-install-suggests \
|
||||||
|
openjdk-25-jdk-headless docker-cli docker-buildx zstd maven
|
||||||
|
|
||||||
|
- name: Cache Maven packages
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.m2/repository
|
||||||
|
**/target/native-image-cache
|
||||||
|
**/target/reports
|
||||||
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
|
restore-keys: ${{ runner.os }}-maven-
|
||||||
|
|
||||||
|
- name: Build native with Maven
|
||||||
|
run: |
|
||||||
|
mvn -B clean install -DskipTests -Pnative \
|
||||||
|
-Dquarkus.container-image.push=false
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Install JDK and Maven
|
||||||
run: |
|
run: |
|
||||||
# Kiểm tra proxy có hoạt động không
|
# Kiểm tra proxy có hoạt động không
|
||||||
echo "Acquire::http::Proxy \"$http_proxy\";" > /etc/apt/apt.conf.d/80proxy
|
echo "Acquire::http::Proxy \"$http_proxy\";" > /etc/apt/apt.conf.d/80proxy
|
||||||
@@ -46,16 +68,13 @@ jobs:
|
|||||||
**/target/native-image-cache
|
**/target/native-image-cache
|
||||||
**/target/reports
|
**/target/reports
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
restore-keys: |
|
restore-keys: ${{ runner.os }}-maven-
|
||||||
${{ runner.os }}-maven-
|
|
||||||
|
|
||||||
- name: Build and Test all module
|
- name: Run Tests
|
||||||
env:
|
env:
|
||||||
TESTCONTAINERS_RYUK_DISABLED: true
|
TESTCONTAINERS_RYUK_DISABLED: true
|
||||||
run: |
|
run: |
|
||||||
mvn -B clean install -Pnative \
|
mvn clean test -Dquarkus.main.run-tests=true
|
||||||
-Dquarkus.container-image.push=false \
|
|
||||||
-Dquarkus.container-image.registry=git.demonkernel.io.vn
|
|
||||||
|
|
||||||
- name: Publish Test Report
|
- name: Publish Test Report
|
||||||
uses: dorny/test-reporter@v3
|
uses: dorny/test-reporter@v3
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class StaffService extends BaseService<StaffEntity> {
|
|||||||
|
|
||||||
return Response.status(Response.Status.CREATED)
|
return Response.status(Response.Status.CREATED)
|
||||||
.header(InternalValue.userId, staff.id.toString())
|
.header(InternalValue.userId, staff.id.toString())
|
||||||
|
.header(InternalValue.managerId, manager.id.toString())
|
||||||
.header(InternalValue.role, Role.Staff)
|
.header(InternalValue.role, Role.Staff)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,7 @@ public class StaffService extends BaseService<StaffEntity> {
|
|||||||
|
|
||||||
return Response.accepted()
|
return Response.accepted()
|
||||||
.header(InternalValue.userId, staff.id.toString())
|
.header(InternalValue.userId, staff.id.toString())
|
||||||
|
.header(InternalValue.managerId, staff.serve.id.toString())
|
||||||
.header(InternalValue.role, Role.Staff)
|
.header(InternalValue.role, Role.Staff)
|
||||||
.entity(staff)
|
.entity(staff)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -10,12 +10,8 @@ import io.quarkus.test.common.QuarkusTestResource;
|
|||||||
import io.quarkus.test.junit.QuarkusTest;
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
import io.quarkus.test.kafka.*;
|
import io.quarkus.test.kafka.*;
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
import io.smallrye.reactive.messaging.kafka.companion.ConsumerTask;
|
|
||||||
import io.smallrye.reactive.messaging.kafka.companion.KafkaCompanion;
|
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import java.time.Duration;
|
|
||||||
import net.datafaker.Faker;
|
import net.datafaker.Faker;
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.drinkool.dtos;
|
||||||
|
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import org.eclipse.microprofile.graphql.NonNull;
|
||||||
|
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CreateShift {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public LocalTime startTime;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public LocalTime endTime;
|
||||||
|
|
||||||
|
public Integer maxStaff;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.drinkool.dtos;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.eclipse.microprofile.graphql.NonNull;
|
||||||
|
|
||||||
|
public class RegisterShift {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public UUID shiftId;
|
||||||
|
}
|
||||||
@@ -27,6 +27,13 @@ public class EateryEntity extends BaseEntity {
|
|||||||
)
|
)
|
||||||
public List<MenuItemEntity> menuItems = new ArrayList<>();
|
public List<MenuItemEntity> menuItems = new ArrayList<>();
|
||||||
|
|
||||||
|
@OneToMany(
|
||||||
|
mappedBy = "eatery",
|
||||||
|
cascade = CascadeType.ALL,
|
||||||
|
orphanRemoval = true
|
||||||
|
)
|
||||||
|
public List<ShiftEntity> shifts = new ArrayList<>();
|
||||||
|
|
||||||
public EateryEntity() {}
|
public EateryEntity() {}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.drinkool.entities;
|
||||||
|
|
||||||
|
import com.drinkool.lib.entities.BaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table
|
||||||
|
public class ShiftEntity extends BaseEntity {
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "eatery_id")
|
||||||
|
public EateryEntity eatery;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
public LocalTime startTime;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
public LocalTime endTime;
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
public Integer maxStaff;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.drinkool.entities;
|
||||||
|
|
||||||
|
import com.drinkool.lib.entities.BaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table
|
||||||
|
public class ShiftRegistrationEntity extends BaseEntity {
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
public UUID staffId;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
public ShiftEntity shift;
|
||||||
|
|
||||||
|
public static long countByShift(UUID shiftId) {
|
||||||
|
return count("shift.id = ?1", shiftId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ShiftRegistrationEntity> findOverlappingShifts(
|
||||||
|
UUID staffId,
|
||||||
|
ShiftEntity registratingShift
|
||||||
|
) {
|
||||||
|
return list(
|
||||||
|
"staffId = ?1 and shift.startTime < ?2 and shift.endTime > ?3",
|
||||||
|
staffId,
|
||||||
|
registratingShift.endTime,
|
||||||
|
registratingShift.startTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import com.drinkool.InternalValue;
|
||||||
|
import com.drinkool.Role;
|
||||||
|
import com.drinkool.dtos.CreateShift;
|
||||||
|
import com.drinkool.dtos.RegisterShift;
|
||||||
|
import com.drinkool.entities.EateryEntity;
|
||||||
|
import com.drinkool.entities.ShiftEntity;
|
||||||
|
import com.drinkool.entities.ShiftRegistrationEntity;
|
||||||
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.eclipse.microprofile.graphql.*;
|
||||||
|
|
||||||
|
@GraphQLApi
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ShiftService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HttpServerRequest request;
|
||||||
|
|
||||||
|
// --- QUERIES ---
|
||||||
|
|
||||||
|
@Query("allShifts")
|
||||||
|
@Description("Lấy danh sách tất cả các loại ca trực")
|
||||||
|
@RolesAllowed({ Role.Staff, Role.Manager })
|
||||||
|
public List<ShiftEntity> getAllShifts() throws GraphQLException {
|
||||||
|
String ownerId = request.getHeader(InternalValue.managerId);
|
||||||
|
if (ownerId == null || ownerId.isEmpty()) {
|
||||||
|
ownerId = request.getHeader(InternalValue.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
EateryEntity eatery = EateryEntity.findByOwnerId(ownerId);
|
||||||
|
|
||||||
|
return ShiftEntity.list("eatery", eatery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("myRegistrations")
|
||||||
|
@Description("Lấy danh sách ca trực mà nhân viên hiện tại đã đăng ký")
|
||||||
|
@RolesAllowed(Role.Staff)
|
||||||
|
public List<ShiftRegistrationEntity> getMyShifts() {
|
||||||
|
String userId = request.getHeader(InternalValue.userId);
|
||||||
|
return ShiftRegistrationEntity.list("staffId", UUID.fromString(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("allRegistrations")
|
||||||
|
@Description("Manager xem toàn bộ danh sách đăng ký của tất cả nhân viên")
|
||||||
|
@RolesAllowed(Role.Manager)
|
||||||
|
public List<ShiftRegistrationEntity> getAllRegistrations() {
|
||||||
|
return ShiftRegistrationEntity.listAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- MUTATIONS ---
|
||||||
|
|
||||||
|
@Mutation("registerShift")
|
||||||
|
@Description("Nhân viên đăng ký vào một ca trực cụ thể")
|
||||||
|
@RolesAllowed(Role.Staff)
|
||||||
|
@Transactional
|
||||||
|
public ShiftRegistrationEntity registerShift(
|
||||||
|
@Name("registration") RegisterShift registration
|
||||||
|
) throws GraphQLException {
|
||||||
|
String userIdStr = request.getHeader(InternalValue.userId);
|
||||||
|
String managerIdStr = request.getHeader(InternalValue.managerId);
|
||||||
|
UUID staffId = UUID.fromString(userIdStr);
|
||||||
|
|
||||||
|
EateryEntity eatery = EateryEntity.findByOwnerId(managerIdStr);
|
||||||
|
if (eatery == null) throw new GraphQLException("NotFoundEatery");
|
||||||
|
|
||||||
|
ShiftEntity shift = ShiftEntity.find(
|
||||||
|
"id = ?1 and eatery.id = ?2",
|
||||||
|
registration.shiftId,
|
||||||
|
eatery.id
|
||||||
|
).firstResult();
|
||||||
|
if (shift == null) throw new GraphQLException("NotFoundShift");
|
||||||
|
|
||||||
|
List<ShiftRegistrationEntity> overlappingShifts =
|
||||||
|
ShiftRegistrationEntity.findOverlappingShifts(staffId, shift);
|
||||||
|
|
||||||
|
if (!overlappingShifts.isEmpty()) {
|
||||||
|
String ids = overlappingShifts
|
||||||
|
.stream()
|
||||||
|
.map(reg -> reg.shift.id.toString())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
throw new GraphQLException("OverlappingShift: " + ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentStaffCount = ShiftRegistrationEntity.countByShift(shift.id);
|
||||||
|
if (shift.maxStaff != null && currentStaffCount >= shift.maxStaff) {
|
||||||
|
throw new GraphQLException("FulledShift");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftRegistrationEntity record = new ShiftRegistrationEntity();
|
||||||
|
record.staffId = staffId;
|
||||||
|
record.shift = shift;
|
||||||
|
record.persist();
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation("createShift")
|
||||||
|
@Description("Quản lý tạo mới một loại ca trực")
|
||||||
|
@RolesAllowed(Role.Manager)
|
||||||
|
@Transactional
|
||||||
|
public ShiftEntity createShift(@Name("shiftInput") CreateShift input)
|
||||||
|
throws GraphQLException {
|
||||||
|
String ownerId = request.getHeader(InternalValue.userId);
|
||||||
|
|
||||||
|
EateryEntity eatery = EateryEntity.findByOwnerId(ownerId);
|
||||||
|
if (eatery == null) return null;
|
||||||
|
|
||||||
|
ShiftEntity shift = new ShiftEntity();
|
||||||
|
shift.eatery = eatery;
|
||||||
|
shift.name = input.name;
|
||||||
|
shift.startTime = input.startTime;
|
||||||
|
shift.endTime = input.endTime;
|
||||||
|
shift.maxStaff = input.maxStaff;
|
||||||
|
|
||||||
|
if (shift.endTime.isBefore(shift.startTime)) {
|
||||||
|
throw new GraphQLException("InvalidEndTime");
|
||||||
|
}
|
||||||
|
|
||||||
|
shift.persist();
|
||||||
|
return shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation("updateShift")
|
||||||
|
@Description("Quản lý cập nhật thông tin ca trực")
|
||||||
|
@RolesAllowed(Role.Manager)
|
||||||
|
@Transactional
|
||||||
|
public ShiftEntity updateShift(
|
||||||
|
@Name("id") UUID id,
|
||||||
|
@Name("shiftInput") CreateShift input
|
||||||
|
) throws GraphQLException {
|
||||||
|
ShiftEntity shift = ShiftEntity.findById(id);
|
||||||
|
if (shift == null) throw new GraphQLException("NotFoundShift");
|
||||||
|
|
||||||
|
String ownerId = request.getHeader(InternalValue.userId);
|
||||||
|
if (
|
||||||
|
!shift.eatery.ownerId.toString().equals(ownerId)
|
||||||
|
) throw new GraphQLException("InvalidShiftId");
|
||||||
|
|
||||||
|
shift.name = input.name;
|
||||||
|
shift.startTime = input.startTime;
|
||||||
|
shift.endTime = input.endTime;
|
||||||
|
shift.maxStaff = input.maxStaff;
|
||||||
|
|
||||||
|
if (shift.endTime.isBefore(shift.startTime)) {
|
||||||
|
throw new GraphQLException("InvalidEndTime");
|
||||||
|
}
|
||||||
|
|
||||||
|
return shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation("deleteShift")
|
||||||
|
@Description("Quản lý xóa một ca trực")
|
||||||
|
@RolesAllowed(Role.Manager)
|
||||||
|
@Transactional
|
||||||
|
public boolean deleteShift(@Name("id") UUID id) throws GraphQLException {
|
||||||
|
String ownerId = request.getHeader(InternalValue.userId);
|
||||||
|
|
||||||
|
EateryEntity eatery = EateryEntity.findByOwnerId(ownerId);
|
||||||
|
if (eatery == null) {
|
||||||
|
throw new GraphQLException("NotFoundEatery");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftEntity shift = ShiftEntity.find(
|
||||||
|
"id = ?1 and eatery = ?2",
|
||||||
|
id,
|
||||||
|
eatery
|
||||||
|
).firstResult();
|
||||||
|
if (shift == null) {
|
||||||
|
throw new GraphQLException("NotFoundShiftOrAccessDenied");
|
||||||
|
}
|
||||||
|
|
||||||
|
long count = ShiftRegistrationEntity.count("shift.id", id);
|
||||||
|
if (count > 0) throw new GraphQLException(
|
||||||
|
"CannotDeleteShiftWithRegistrations"
|
||||||
|
);
|
||||||
|
|
||||||
|
return ShiftEntity.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation("cancelRegistration")
|
||||||
|
@Description("Nhân viên hủy ca đã đăng ký")
|
||||||
|
@RolesAllowed(Role.Staff)
|
||||||
|
@Transactional
|
||||||
|
public boolean cancelRegistration(@Name("registrationId") UUID id)
|
||||||
|
throws GraphQLException {
|
||||||
|
String userId = request.getHeader(InternalValue.userId);
|
||||||
|
ShiftRegistrationEntity reg = ShiftRegistrationEntity.findById(id);
|
||||||
|
|
||||||
|
if (reg == null) throw new GraphQLException("RegistrationNotFound");
|
||||||
|
if (!reg.staffId.equals(UUID.fromString(userId))) {
|
||||||
|
throw new GraphQLException("NotYourRegistration");
|
||||||
|
}
|
||||||
|
|
||||||
|
reg.delete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import com.drinkool.InternalValue;
|
||||||
|
import com.drinkool.Role;
|
||||||
|
import com.drinkool.entities.EateryEntity;
|
||||||
|
import com.drinkool.entities.ShiftEntity;
|
||||||
|
import com.drinkool.entities.ShiftRegistrationEntity;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
public class ShiftServiceTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
@Transactional
|
||||||
|
void setup() {
|
||||||
|
ShiftRegistrationEntity.deleteAll();
|
||||||
|
ShiftEntity.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TEST QUERIES ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAllShifts() {
|
||||||
|
UUID ownerId = createMockOwner();
|
||||||
|
EateryEntity eatery = createMockEatery(ownerId);
|
||||||
|
|
||||||
|
createMockShift("Ca Sáng", "08:00:00", "12:00:00", eatery);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.header(InternalValue.role, Role.Manager)
|
||||||
|
.header(InternalValue.userId, ownerId)
|
||||||
|
.contentType("application/json")
|
||||||
|
.body("{ \"query\": \"{ allShifts { name startTime } }\" }")
|
||||||
|
.when()
|
||||||
|
.post("/graphql")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("data.allShifts", hasSize(1))
|
||||||
|
.body("data.allShifts[0].name", is("Ca Sáng"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TEST MUTATIONS ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateShift_AsManager() {
|
||||||
|
String query = """
|
||||||
|
mutation Create($input: CreateShiftInput!)
|
||||||
|
{ createShift(shiftInput: $input) { id name } }""";
|
||||||
|
|
||||||
|
Map<String, Object> input = new HashMap<>();
|
||||||
|
input.put("name", "Ca Sáng");
|
||||||
|
input.put("startTime", "08:00:00");
|
||||||
|
input.put("endTime", "12:00:00");
|
||||||
|
input.put("maxStaff", 5);
|
||||||
|
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("query", query);
|
||||||
|
body.put("variables", Map.of("input", input));
|
||||||
|
|
||||||
|
UUID ownerId = createMockOwner();
|
||||||
|
createMockEatery(ownerId);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType("application/json")
|
||||||
|
.header(InternalValue.role, Role.Manager)
|
||||||
|
.header(InternalValue.userId, ownerId)
|
||||||
|
.body(body)
|
||||||
|
.when()
|
||||||
|
.post("/graphql")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("data.createShift.name", is("Ca Sáng"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegisterShift_Success() {
|
||||||
|
UUID ownerId = createMockOwner();
|
||||||
|
EateryEntity eatery = createMockEatery(ownerId);
|
||||||
|
|
||||||
|
ShiftEntity shift = createMockShift(
|
||||||
|
"Ca Tối",
|
||||||
|
"18:00:00",
|
||||||
|
"22:00:00",
|
||||||
|
eatery
|
||||||
|
);
|
||||||
|
UUID staffId = UUID.randomUUID();
|
||||||
|
|
||||||
|
String mutation = String.format(
|
||||||
|
"mutation { registerShift(registration: { shiftId: \\\"%s\\\" }) { id staffId } }",
|
||||||
|
shift.id
|
||||||
|
);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType("application/json")
|
||||||
|
.header(InternalValue.role, Role.Staff)
|
||||||
|
.header(InternalValue.userId, staffId.toString())
|
||||||
|
.header(InternalValue.managerId, ownerId.toString())
|
||||||
|
.body("{ \"query\": \"" + mutation + "\" }")
|
||||||
|
.when()
|
||||||
|
.post("/graphql")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("data.registerShift.staffId", is(staffId.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegisterShift_Overlap_Fail() {
|
||||||
|
UUID ownerId = createMockOwner();
|
||||||
|
EateryEntity eatery = createMockEatery(ownerId);
|
||||||
|
UUID staffId = UUID.randomUUID();
|
||||||
|
|
||||||
|
// Tạo 2 ca bị trùng giờ nhau
|
||||||
|
ShiftEntity shift1 = createMockShift(
|
||||||
|
"Ca 1",
|
||||||
|
"08:00:00",
|
||||||
|
"12:00:00",
|
||||||
|
eatery
|
||||||
|
);
|
||||||
|
ShiftEntity shift2 = createMockShift(
|
||||||
|
"Ca 2",
|
||||||
|
"10:00:00",
|
||||||
|
"14:00:00",
|
||||||
|
eatery
|
||||||
|
);
|
||||||
|
|
||||||
|
// Đăng ký ca 1 trước
|
||||||
|
registerStaffToShift(staffId, shift1);
|
||||||
|
|
||||||
|
// Đăng ký ca 2 (sẽ bị overlap)
|
||||||
|
String mutation = String.format(
|
||||||
|
"mutation { registerShift(registration: { shiftId: \\\"%s\\\" }) { id } }",
|
||||||
|
shift2.id
|
||||||
|
);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType("application/json")
|
||||||
|
.header(InternalValue.userId, staffId.toString())
|
||||||
|
.header(InternalValue.managerId, ownerId.toString())
|
||||||
|
.header(InternalValue.role, Role.Staff)
|
||||||
|
.body("{ \"query\": \"" + mutation + "\" }")
|
||||||
|
.when()
|
||||||
|
.post("/graphql")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("errors", notNullValue())
|
||||||
|
.body("errors[0].message", containsString("OverlappingShift"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteShift_WithRegistrations_Fail() {
|
||||||
|
UUID ownerId = createMockOwner();
|
||||||
|
EateryEntity eatery = createMockEatery(ownerId);
|
||||||
|
ShiftEntity shift = createMockShift(
|
||||||
|
"Ca Xóa",
|
||||||
|
"08:00:00",
|
||||||
|
"12:00:00",
|
||||||
|
eatery
|
||||||
|
);
|
||||||
|
registerStaffToShift(UUID.randomUUID(), shift);
|
||||||
|
|
||||||
|
String mutation = String.format(
|
||||||
|
"mutation { deleteShift(id: \\\"%s\\\") }",
|
||||||
|
shift.id
|
||||||
|
);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.header(InternalValue.userId, ownerId.toString())
|
||||||
|
.header(InternalValue.role, Role.Manager)
|
||||||
|
.contentType("application/json")
|
||||||
|
.body("{ \"query\": \"" + mutation + "\" }")
|
||||||
|
.when()
|
||||||
|
.post("/graphql")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("errors[0].message", is("CannotDeleteShiftWithRegistrations"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HELPER METHODS ---
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
ShiftEntity createMockShift(
|
||||||
|
String name,
|
||||||
|
String start,
|
||||||
|
String end,
|
||||||
|
EateryEntity eatery
|
||||||
|
) {
|
||||||
|
ShiftEntity shift = new ShiftEntity();
|
||||||
|
shift.eatery = eatery;
|
||||||
|
shift.name = name;
|
||||||
|
shift.startTime = LocalTime.parse(start);
|
||||||
|
shift.endTime = LocalTime.parse(end);
|
||||||
|
shift.maxStaff = 5;
|
||||||
|
shift.persist();
|
||||||
|
return shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
void registerStaffToShift(UUID staffId, ShiftEntity shift) {
|
||||||
|
ShiftRegistrationEntity reg = new ShiftRegistrationEntity();
|
||||||
|
reg.staffId = staffId;
|
||||||
|
reg.shift = shift;
|
||||||
|
reg.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public UUID createMockOwner() {
|
||||||
|
return UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public EateryEntity createMockEatery(UUID ownerId) {
|
||||||
|
EateryEntity eatery = new EateryEntity();
|
||||||
|
eatery.name = "Nhà hàng " + ownerId.toString().substring(0, 5);
|
||||||
|
eatery.ownerId = ownerId;
|
||||||
|
eatery.persist();
|
||||||
|
|
||||||
|
EateryEntity.getEntityManager().flush();
|
||||||
|
return eatery;
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-15
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project
|
<project
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||||
>
|
>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@@ -20,11 +20,9 @@
|
|||||||
<compiler-plugin.version>3.15.0</compiler-plugin.version>
|
<compiler-plugin.version>3.15.0</compiler-plugin.version>
|
||||||
<maven.compiler.release>25</maven.compiler.release>
|
<maven.compiler.release>25</maven.compiler.release>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
>UTF-8</project.reporting.outputEncoding>
|
|
||||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||||
<quarkus.platform.group-id
|
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||||
>io.quarkus.platform</quarkus.platform.group-id>
|
|
||||||
<quarkus.platform.version>3.32.4</quarkus.platform.version>
|
<quarkus.platform.version>3.32.4</quarkus.platform.version>
|
||||||
<skipITs>true</skipITs>
|
<skipITs>true</skipITs>
|
||||||
<surefire-plugin.version>3.5.4</surefire-plugin.version>
|
<surefire-plugin.version>3.5.4</surefire-plugin.version>
|
||||||
@@ -55,6 +53,10 @@
|
|||||||
<version>${org.projectlombok.version}</version>
|
<version>${org.projectlombok.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-smallrye-openapi</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-security</artifactId>
|
<artifactId>quarkus-security</artifactId>
|
||||||
@@ -128,8 +130,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<argLine>@{argLine}</argLine>
|
<argLine>@{argLine}</argLine>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<java.util.logging.manager
|
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||||
>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
|
||||||
<maven.home>${maven.home}</maven.home>
|
<maven.home>${maven.home}</maven.home>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
<forkCount>1</forkCount>
|
<forkCount>1</forkCount>
|
||||||
@@ -151,10 +152,9 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<argLine>@{argLine}</argLine>
|
<argLine>@{argLine}</argLine>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<native.image.path
|
<native.image.path>
|
||||||
>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
|
${project.build.directory}/${project.build.finalName}-runner</native.image.path>
|
||||||
<java.util.logging.manager
|
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||||
>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
|
||||||
<maven.home>${maven.home}</maven.home>
|
<maven.home>${maven.home}</maven.home>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -176,4 +176,4 @@
|
|||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
</project>
|
</project>
|
||||||
@@ -11,6 +11,9 @@ quarkus.container-image.additional-tags=latest
|
|||||||
quarkus.native.container-build=true
|
quarkus.native.container-build=true
|
||||||
quarkus.native.remote-container-build=true
|
quarkus.native.remote-container-build=true
|
||||||
quarkus.native.additional-build-args=--future-defaults=all
|
quarkus.native.additional-build-args=--future-defaults=all
|
||||||
|
quarkus.swagger-ui.always-include=false
|
||||||
|
quarkus.swagger-ui.request-interceptor=true
|
||||||
|
quarkus.swagger-ui.path=/api
|
||||||
|
|
||||||
# Rest Client
|
# Rest Client
|
||||||
quarkus.rest-client.account.url=http://account-service
|
quarkus.rest-client.account.url=http://account-service
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ public class InternalValue {
|
|||||||
public static final String headerId = "X-Internal-";
|
public static final String headerId = "X-Internal-";
|
||||||
public static final String userId = headerId + "UserId";
|
public static final String userId = headerId + "UserId";
|
||||||
public static final String role = headerId + "Role";
|
public static final String role = headerId + "Role";
|
||||||
|
public static final String managerId = headerId + "ManagerId";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mvn clean package -DskipTests \
|
mvn clean package -DskipTests \
|
||||||
|
-Dquarkus.swagger-ui.always-include=true \
|
||||||
-Dquarkus.container-image.push=false \
|
-Dquarkus.container-image.push=false \
|
||||||
-Dquarkus.container-image.registry=git.demonkernel.io.vn \
|
-Dquarkus.container-image.registry=git.demonkernel.io.vn \
|
||||||
"$@"
|
"$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user