From 434715b93856b5b11af425465612ad2144dfcdf6 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 4 Aug 2026 12:57:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- file/AGENTS.md | 1 + file/README.md | 125 ++++++++++++++++++ order/README-ORDER.md | 6 +- .../PlatformFirstLegOrderController.java | 15 ++- .../order/repository/OrderRepository.java | 4 +- .../service/OrderApplicationService.java | 2 +- .../baiyue/order/service/OrderScopeGuard.java | 1 + .../src/main/resources/openapi/order-v1.yaml | 12 ++ .../OrderControllerPermissionTest.java | 2 +- .../PlatformOrderPageControllerTest.java | 72 ++++++++++ .../PlatformOrderPageRepositoryTest.java | 56 ++++++++ .../service/PlatformOrderPageServiceTest.java | 27 ++++ 12 files changed, 311 insertions(+), 12 deletions(-) create mode 100644 file/README.md create mode 100644 order/src/test/java/com/baiyue/order/controller/PlatformOrderPageControllerTest.java create mode 100644 order/src/test/java/com/baiyue/order/repository/PlatformOrderPageRepositoryTest.java create mode 100644 order/src/test/java/com/baiyue/order/service/PlatformOrderPageServiceTest.java diff --git a/file/AGENTS.md b/file/AGENTS.md index 79bec75e6..fb5758dd0 100644 --- a/file/AGENTS.md +++ b/file/AGENTS.md @@ -12,6 +12,7 @@ | 功能 | 入口 | |---|---| +| 模块使用、RocketMQ 部署与导出排查 | `README.md`;完整架构与接口契约见 `../docs/公共导出与下载中心接入说明.md` | | 商户资质上传与读取 | `src/main/java/com/baiyue/file/controller/QualificationFileController.java` | | 平台资质上传、替换与查看 | `controller/PlatformQualificationFileController.java`;`POST /{tenantId}` 支持 `qualificationType`、`certificateNo`、`validTo`、`replace`,下载为 `GET /{tenantId}/{fileName}` | | 服务状态 | `controller/FileHealthController.java` | diff --git a/file/README.md b/file/README.md new file mode 100644 index 000000000..ce4ae894d --- /dev/null +++ b/file/README.md @@ -0,0 +1,125 @@ +# File Service 使用与运维说明 + +File Service 同时承载企业资质文件和公共导出下载中心。公共导出采用数据库任务表、事务 Outbox、RocketMQ 和业务 Provider;完整架构与接口契约见 [`../docs/公共导出与下载中心接入说明.md`](../docs/公共导出与下载中心接入说明.md)。 + +## 公共导出运行条件 + +创建导出任务只会写入 `file_export_task` 和 `file_export_outbox`。以下三个组件全部启用后,任务才会从 `PENDING` 继续执行: + +```dotenv +FILE_EXPORT_RECONCILER_ENABLED=true +FILE_EXPORT_PUBLISHER_ENABLED=true +FILE_EXPORT_CONSUMER_ENABLED=true +FILE_EXPORT_CONSUMER_CONCURRENCY=4 +``` + +三个开关默认均为 `false`,避免 RocketMQ 尚未就绪时 File Service 启动失败。Docker 容器只在创建时读取环境变量;修改 Jenkins 环境文件后必须重新部署 File Service,`docker restart` 不会更新配置。 + +当前测试环境由 Jenkins 文件凭据 `firstleg-test-env` 注入这些变量;凭据内容变更后重新执行 `file` 服务的部署阶段。 + +测试环境还必须配置: + +```dotenv +INFRA_NETWORK=firstleg-test-infra-network +ROCKETMQ_NAME_SERVER=rocketmq-namesrv:9876 +ROCKETMQ_BROKER_IP=rocketmq-broker +MQ_TOPIC_PREFIX=test- +ROCKETMQ_DATA_DIR=/data/firstleg/rocketmq/store +``` + +File 与 RocketMQ 不暴露 MQ 宿主机端口,二者通过 `infra-network` 和 Docker 服务名通信。`ROCKETMQ_BROKER_IP` 不得填写 `localhost`、`127.0.0.1` 或宿主机公网地址。 + +## 测试环境首次部署 RocketMQ + +在服务器的稳定仓库目录执行,不要依赖会被 Jenkins 清理的临时工作区: + +```bash +cd /www/deploy/baiyue-firstleg +mkdir -p /data/firstleg/rocketmq/store +chown -R 3000:3000 /data/firstleg/rocketmq/store + +DEPLOY_ENV=test \ +INFRA_NETWORK=firstleg-test-infra-network \ +ROCKETMQ_BROKER_IP=rocketmq-broker \ +ROCKETMQ_DATA_DIR=/data/firstleg/rocketmq/store \ +MQ_TOPIC_PREFIX=test- \ +docker compose -f deploy/docker-compose.rocketmq.yml up -d +``` + +官方 `apache/rocketmq:5.3.1` 镜像使用 UID/GID `3000` 的 `rocketmq` 用户。宿主机存储目录若属于 `root:root` 且不可写,Broker 会反复重启;不要把 Broker 改成 root 用户,应修正挂载目录所有权。 + +部署后的正常状态: + +```text +rocketmq-namesrv Up +rocketmq-broker Up +rocketmq-topic-init Exited (0) +``` + +`rocketmq-topic-init` 是一次性初始化容器,`Exited (0)` 表示 Topic、消费组和 DLQ 初始化成功。 + +验证 Broker 注册结果: + +```bash +docker run --rm \ + --network firstleg-test-infra-network \ + apache/rocketmq:5.3.1 \ + sh mqadmin clusterList -n rocketmq-namesrv:9876 +``` + +输出必须包含 `BaiyueCluster`、`baiyue-file-export-broker` 和可访问地址 `rocketmq-broker:10911`。 + +## 分阶段启用导出 + +首次上线按以下顺序逐步修改环境文件,每一步都重新部署 File Service 并确认容器 `healthy`、`restartCount=0`: + +1. Reconciler:`true / false / false` +2. Reconciler + Publisher:`true / true / false` +3. Reconciler + Publisher + Consumer:`true / true / true` + +顺序均为 `RECONCILER / PUBLISHER / CONSUMER`。第二阶段应观察到 Outbox 从 `PENDING` 变为 `SENT`;第三阶段任务应从 `PENDING` 进入 `RUNNING`,最终进入 `SUCCEEDED` 或带明确原因的 `FAILED`。已有的 `PENDING` 任务无需重建或手工改状态。 + +## 快速排查 + +任务状态查询: + +```sql +SELECT id, task_no, status, dispatch_version, attempt_count, + processed_rows, next_retry_at, lease_owner, lease_until, + requested_at, started_at, completed_at, failure_message +FROM file_export_task +WHERE id = ?; +``` + +Outbox 查询: + +```sql +SELECT id, task_id, dispatch_version, topic, status, attempt_count, + next_retry_at, lease_owner, lease_until, sent_at, last_error +FROM file_export_outbox +WHERE task_id = ? +ORDER BY id; +``` + +| 现象 | 判断与处理 | +|---|---| +| Task `PENDING`、`attempt_count=0`,Outbox `PENDING`、`attempt_count=0` | Publisher 未启用或未运行 | +| Outbox `PENDING` 且有 `last_error` | Publisher 已尝试,检查 NameServer、Broker、Topic 和网络 | +| Outbox `CLAIMED` 且租约过期 | Publisher 中断,确认 Reconciler 已启用 | +| Outbox `SENT`,Task 仍为 `PENDING` | Consumer 未启用、未连接或存在消费积压 | +| File 启动时报 `connect to null failed` | NameServer/Broker 未运行,或未连接同一 `infra-network` | +| Broker 反复重启,数据目录为 `root:root` | 将挂载目录递归调整为 `3000:3000`,不要使用 root 运行 Broker | + +不要手工把 Task 改为 `RUNNING` 或把 Outbox 改为 `SENT`,否则会绕过租约、幂等和消息投递链路。 + +## 验收 + +测试环境主链路验收使用: + +```powershell +$env:ADMIN_ACCESS_TOKEN = '<测试环境平台用户Token>' +.\scripts\export-center-test-env-acceptance.ps1 ` + -GatewayBaseUrl 'http://8.163.30.128:9000' +``` + +脚本会创建真实 `CUSTOMER_LIST` 任务、轮询终态、下载文件并检查 XLSX ZIP 签名。Token 只通过当前进程环境变量注入,不写入仓库文件。 diff --git a/order/README-ORDER.md b/order/README-ORDER.md index 7697f320d..d4f0cdcb4 100644 --- a/order/README-ORDER.md +++ b/order/README-ORDER.md @@ -9,12 +9,12 @@ ## 接口隔离 / API separation - 商户端 `/api/merchant/v1/orders/first-leg`:草稿、编辑、提交、查询、详情、取消。 / Merchant draft, edit, submit, query, detail, and cancel APIs. -- 平台端 `/api/platform/v1/orders/first-leg`:租户订单查询、详情、审核、驳回、受理、取消、开始处理、完成、内部备注。 / Platform tenant query, detail, review, rejection, acceptance, cancellation, processing, completion, and internal notes. +- 平台端 `/api/platform/v1/orders/first-leg`:跨租户订单列表、详情、审核、驳回、受理、取消、开始处理、完成、内部备注。 / Platform cross-tenant list, detail, review, rejection, acceptance, cancellation, processing, completion, and internal notes. - 平台指派 `/api/platform/v1/first-leg/tasks/{taskId}/assignment`。 / Platform fulfillment assignment. -商户接口要求 `appCode=MERCHANT`,租户来自 JWT。平台接口要求 `appCode=PLATFORM`,并显式传入目标 `tenantId`;所有服务层 SQL 都带租户条件。 +商户接口要求 `appCode=MERCHANT`,租户来自 JWT。平台列表接口要求 `appCode=PLATFORM`,省略 `tenantId` 时查询全部客户租户,传入时按单个租户筛选;平台详情和写操作仍必须显式传入目标 `tenantId`。 -Merchant APIs require `appCode=MERCHANT` and derive the tenant from JWT. Platform APIs require `appCode=PLATFORM` and an explicit target `tenantId`; every service query includes the tenant predicate. +Merchant APIs require `appCode=MERCHANT` and derive the tenant from JWT. The platform list requires `appCode=PLATFORM`: omitting `tenantId` queries all customer tenants, while supplying it filters one tenant. Platform detail and write operations still require an explicit target `tenantId`. ## 状态与审计 / State and audit diff --git a/order/src/main/java/com/baiyue/order/controller/PlatformFirstLegOrderController.java b/order/src/main/java/com/baiyue/order/controller/PlatformFirstLegOrderController.java index 0d2ac59c0..5e99b8065 100644 --- a/order/src/main/java/com/baiyue/order/controller/PlatformFirstLegOrderController.java +++ b/order/src/main/java/com/baiyue/order/controller/PlatformFirstLegOrderController.java @@ -31,11 +31,11 @@ import org.springframework.web.bind.annotation.PutMapping; import java.util.List; /** - * 平台端头程订单管理接口,在明确的目标租户范围内提供查询、审核和履约状态流转能力。 + * 平台端头程订单管理接口,提供跨租户列表查询,并在明确的目标租户范围内执行详情查询和状态流转。 */ @Tag( name = "Platform first-leg orders / 平台头程订单", - description = "Platform lifecycle management for orders in an explicit target tenant. / 平台端针对明确目标租户管理头程订单生命周期。" + description = "Platform review queue across tenants; detail and lifecycle actions require an explicit target tenant. / 平台端跨租户审核队列;详情和生命周期操作需要明确目标租户。" ) @RestController @RequestMapping("/api/platform/v1/orders/first-leg") @@ -98,7 +98,7 @@ public class PlatformFirstLegOrderController { return ApiResponse.success(null); } - @Operation(summary = "Tenant order query / 租户订单查询", description = "Queries a page of first-leg orders in the explicit target tenant. / 分页查询明确目标租户下的头程订单。") + @Operation(summary = "Platform order query / 平台订单查询", description = "Queries all tenant orders when tenantId is omitted, or filters one tenant when supplied. / tenantId 省略时分页查询全部客户租户订单,传入时仅查询指定租户。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform scope required / 需要平台端权限") @@ -107,9 +107,14 @@ public class PlatformFirstLegOrderController { @PreAuthorize("hasAuthority('platform:order:list')") public ApiResponse> page( @AuthenticationPrincipal AuthenticatedUser user, - @Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) @RequestParam long tenantId, + @Parameter(description = "Optional target tenant filter / 可选的目标租户筛选", example = "10001") + @RequestParam(required = false) Long tenantId, @Valid OrderRequests.QueryRequest query) { - return ApiResponse.success(service.page(OrderScopeGuard.platform(user, tenantId), query)); + OrderScopeGuard.platform(user); + if (tenantId != null) { + OrderScopeGuard.platform(user, tenantId); + } + return ApiResponse.success(service.page(tenantId, query)); } @GetMapping("/tenant-counts") diff --git a/order/src/main/java/com/baiyue/order/repository/OrderRepository.java b/order/src/main/java/com/baiyue/order/repository/OrderRepository.java index 278576bc0..8c1ac3081 100644 --- a/order/src/main/java/com/baiyue/order/repository/OrderRepository.java +++ b/order/src/main/java/com/baiyue/order/repository/OrderRepository.java @@ -73,9 +73,9 @@ public class OrderRepository { .eq(OrderEntity::getId, orderId)); } - public PageResult page(long tenantId, OrderRequests.QueryRequest query) { + public PageResult page(Long tenantId, OrderRequests.QueryRequest query) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(OrderEntity::getTenantId, tenantId) + .eq(tenantId != null, OrderEntity::getTenantId, tenantId) .eq(query.status() != null, OrderEntity::getStatus, query.status()) .eq(query.customerId() != null, OrderEntity::getCustomerId, query.customerId()) .eq(query.destinationWarehouseId() != null, OrderEntity::getDestinationWarehouseId, query.destinationWarehouseId()) diff --git a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java index ffbf990f6..2f02c1b1e 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java +++ b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java @@ -106,7 +106,7 @@ public class OrderApplicationService { completeIdempotency(idempotency, id); } - public PageResult page(long tenantId, OrderRequests.QueryRequest query) { return repository.page(tenantId, query); } + public PageResult page(Long tenantId, OrderRequests.QueryRequest query) { return repository.page(tenantId, query); } public List countByTenantIds(List tenantIds) { var counts = repository.countByTenantIds(tenantIds); diff --git a/order/src/main/java/com/baiyue/order/service/OrderScopeGuard.java b/order/src/main/java/com/baiyue/order/service/OrderScopeGuard.java index 5ce43f5b4..fe33d3ee3 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderScopeGuard.java +++ b/order/src/main/java/com/baiyue/order/service/OrderScopeGuard.java @@ -5,6 +5,7 @@ import com.baiyue.common.core.api.ResultCode; import com.baiyue.common.core.exce */ public final class OrderScopeGuard { private OrderScopeGuard(){} public static long merchant(AuthenticatedUser u){requireApp(u,"MERCHANT");if(u.tenantId()==null||u.tenantId()<=0)throw new BizException(ResultCode.FORBIDDEN,"Merchant tenant is required");return u.tenantId();} + public static void platform(AuthenticatedUser u){requireApp(u,"PLATFORM");} public static long platform(AuthenticatedUser u,long targetTenantId){requireApp(u,"PLATFORM");if(targetTenantId<=0)throw new BizException(ResultCode.BAD_REQUEST,"Target tenant is required");return targetTenantId;} private static void requireApp(AuthenticatedUser u,String expected){if(u==null||!expected.equals(u.appCode()))throw new BizException(ResultCode.FORBIDDEN,"Wrong application scope");} } diff --git a/order/src/main/resources/openapi/order-v1.yaml b/order/src/main/resources/openapi/order-v1.yaml index e25726b43..3284b34dd 100644 --- a/order/src/main/resources/openapi/order-v1.yaml +++ b/order/src/main/resources/openapi/order-v1.yaml @@ -27,6 +27,18 @@ paths: responses: '200': {description: 成功或业务失败,按响应体 code 判断} /api/platform/v1/orders/first-leg: + get: + summary: 平台分页查询头程订单 + description: tenantId 省略时查询全部客户租户订单,传入时仅查询指定租户。 + parameters: + - name: tenantId + in: query + required: false + description: 可选的目标租户筛选 + schema: {type: integer, format: int64, minimum: 1} + responses: + '200': {description: 查询成功或业务失败,按响应体 code 判断} + '403': {description: 需要 platform:order:list 权限和平台身份} post: summary: 平台代客创建订单草稿 parameters: diff --git a/order/src/test/java/com/baiyue/order/controller/OrderControllerPermissionTest.java b/order/src/test/java/com/baiyue/order/controller/OrderControllerPermissionTest.java index e06c3e71d..24939b033 100644 --- a/order/src/test/java/com/baiyue/order/controller/OrderControllerPermissionTest.java +++ b/order/src/test/java/com/baiyue/order/controller/OrderControllerPermissionTest.java @@ -24,7 +24,7 @@ class OrderControllerPermissionTest { @Test void platformOrderListAndDetailUseConfiguredListPermission() throws Exception { assertAuthority(PlatformFirstLegOrderController.class, "page", "hasAuthority('platform:order:list')", - AuthenticatedUser.class, long.class, OrderRequests.QueryRequest.class); + AuthenticatedUser.class, Long.class, OrderRequests.QueryRequest.class); assertAuthority(PlatformFirstLegOrderController.class, "detail", "hasAuthority('platform:order:list')", AuthenticatedUser.class, long.class, long.class); assertAuthority(PlatformFirstLegOrderController.class, "tenantCounts", "hasAuthority('platform:order:list')", diff --git a/order/src/test/java/com/baiyue/order/controller/PlatformOrderPageControllerTest.java b/order/src/test/java/com/baiyue/order/controller/PlatformOrderPageControllerTest.java new file mode 100644 index 000000000..1ffc4573e --- /dev/null +++ b/order/src/test/java/com/baiyue/order/controller/PlatformOrderPageControllerTest.java @@ -0,0 +1,72 @@ +package com.baiyue.order.controller; + +import com.baiyue.common.core.exception.BizException; +import com.baiyue.common.security.AuthenticatedUser; +import com.baiyue.order.dto.OrderRequests; +import com.baiyue.order.service.OrderApplicationService; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +class PlatformOrderPageControllerTest { + private final OrderRequests.QueryRequest query = new OrderRequests.QueryRequest( + null, null, null, null, null, null, 1, 20); + + @Test + void omittedTenantQueriesAcrossAllTenantsForPlatformUser() { + OrderApplicationService service = mock(OrderApplicationService.class); + PlatformFirstLegOrderController controller = controller(service); + + controller.page(user("PLATFORM"), null, query); + + verify(service).page(null, query); + } + + @Test + void suppliedTenantFiltersOneTenantForPlatformUser() { + OrderApplicationService service = mock(OrderApplicationService.class); + PlatformFirstLegOrderController controller = controller(service); + + controller.page(user("PLATFORM"), 2084455952401629185L, query); + + verify(service).page(2084455952401629185L, query); + } + + @Test + void invalidTenantFilterIsRejectedBeforeQuery() { + OrderApplicationService service = mock(OrderApplicationService.class); + PlatformFirstLegOrderController controller = controller(service); + + assertThatThrownBy(() -> controller.page(user("PLATFORM"), 0L, query)) + .isInstanceOf(BizException.class) + .hasMessageContaining("Target tenant is required"); + verifyNoInteractions(service); + } + + @Test + void merchantCannotUseCrossTenantPlatformList() { + OrderApplicationService service = mock(OrderApplicationService.class); + PlatformFirstLegOrderController controller = controller(service); + + assertThatThrownBy(() -> controller.page(user("MERCHANT"), null, query)) + .isInstanceOf(BizException.class) + .hasMessageContaining("Wrong application scope"); + verifyNoInteractions(service); + } + + private PlatformFirstLegOrderController controller(OrderApplicationService service) { + PlatformFirstLegOrderController controller = new PlatformFirstLegOrderController(); + ReflectionTestUtils.setField(controller, "service", service); + return controller; + } + + private AuthenticatedUser user(String appCode) { + return new AuthenticatedUser(1L, null, null, null, "operator", appCode, "WEB", Set.of()); + } +} diff --git a/order/src/test/java/com/baiyue/order/repository/PlatformOrderPageRepositoryTest.java b/order/src/test/java/com/baiyue/order/repository/PlatformOrderPageRepositoryTest.java new file mode 100644 index 000000000..08316e1d8 --- /dev/null +++ b/order/src/test/java/com/baiyue/order/repository/PlatformOrderPageRepositoryTest.java @@ -0,0 +1,56 @@ +package com.baiyue.order.repository; + +import com.baiyue.order.dto.OrderRequests; +import com.baiyue.order.entity.OrderEntity; +import com.baiyue.order.mapper.OrderCargoMapper; +import com.baiyue.order.mapper.OrderChildPurgeMapper; +import com.baiyue.order.mapper.OrderIdempotencyRecordMapper; +import com.baiyue.order.mapper.OrderInternalNoteMapper; +import com.baiyue.order.mapper.OrderMapper; +import com.baiyue.order.mapper.OrderPartyMapper; +import com.baiyue.order.mapper.OrderStatusHistoryMapper; +import com.baiyue.order.mapper.OutboxEventMapper; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PlatformOrderPageRepositoryTest { + private final OrderRequests.QueryRequest query = new OrderRequests.QueryRequest( + null, null, null, null, null, null, 1, 20); + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void tenantPredicateIsAddedOnlyWhenFilterIsSupplied() { + OrderMapper mapper = mock(OrderMapper.class); + when(mapper.selectPage(any(Page.class), any(Wrapper.class))).thenReturn(new Page<>()); + OrderRepository repository = repository(mapper); + + repository.page(null, query); + repository.page(20002L, query); + + ArgumentCaptor> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class); + verify(mapper, org.mockito.Mockito.times(2)).selectPage(any(Page.class), wrapperCaptor.capture()); + assertThat(wrapperCaptor.getAllValues().get(0).getExpression().getNormal()).isEmpty(); + assertThat(wrapperCaptor.getAllValues().get(1).getExpression().getNormal()).isNotEmpty(); + } + + private OrderRepository repository(OrderMapper mapper) { + return new OrderRepository( + mapper, + mock(OrderCargoMapper.class), + mock(OrderPartyMapper.class), + mock(OrderInternalNoteMapper.class), + mock(OrderStatusHistoryMapper.class), + mock(OutboxEventMapper.class), + mock(OrderChildPurgeMapper.class), + mock(OrderIdempotencyRecordMapper.class) + ); + } +} diff --git a/order/src/test/java/com/baiyue/order/service/PlatformOrderPageServiceTest.java b/order/src/test/java/com/baiyue/order/service/PlatformOrderPageServiceTest.java new file mode 100644 index 000000000..28a61ee11 --- /dev/null +++ b/order/src/test/java/com/baiyue/order/service/PlatformOrderPageServiceTest.java @@ -0,0 +1,27 @@ +package com.baiyue.order.service; + +import com.baiyue.order.dto.OrderRequests; +import com.baiyue.order.repository.OrderRepository; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class PlatformOrderPageServiceTest { + private final OrderRequests.QueryRequest query = new OrderRequests.QueryRequest( + null, null, null, null, null, null, 1, 20); + + @Test + void passesOptionalTenantScopeToRepository() { + OrderRepository repository = mock(OrderRepository.class); + OrderApplicationService service = new OrderApplicationService(); + ReflectionTestUtils.setField(service, "repository", repository); + + service.page(null, query); + service.page(20002L, query); + + verify(repository).page(null, query); + verify(repository).page(20002L, query); + } +}