mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-14 20:21:23 +08:00
fix: make plugin hooks run on Node 21+ and green the suite under modern Node (#2184)
ROOT CAUSE: hooks load plugin-hook-bootstrap.js via `node -e "...; process.argv.splice(1,0,s); require(s)"`. On Node 21+, require.main is `undefined` under --eval, so the `if (require.main === module)` guard was false and main() never ran — every plugin hook silently no-op'd (e.g. the MCP-health PreToolUse hook stopped blocking). CI (Node 18/20) hid this; it only surfaces on Node 21+. Fix: also run main() when require.main is undefined (the eval-bootstrap case), while staying dormant on real imports. Also clears pre-existing main debt the full local suite enforces: - catalog:sync — README/docs agent+skill counts drifted after recent merges - tests/ci/supply-chain-watch-workflow: update checkout SHA to the merged v6.0.3 (#2183) - markdownlint + check-unicode-safety --write across docs/skills Suite: 2683/2683 green under Node v25; lint + unicode clean. Co-authored-by: ECC Test <ecc@example.test>
This commit is contained in:
@@ -119,7 +119,7 @@ async function main() {
|
||||
}
|
||||
|
||||
// stdio トランスポートを介して agentpay MCP サーバーに接続する。
|
||||
// サーバーが必要とする env 変数のみをホワイトリストに登録する —
|
||||
// サーバーが必要とする env 変数のみをホワイトリストに登録する —
|
||||
// 秘密鍵を管理するサードパーティのサブプロセスに process.env のすべてを渡さない。
|
||||
const transport = new StdioClientTransport({
|
||||
command: "npx",
|
||||
|
||||
@@ -96,11 +96,11 @@ less pi-hole-install.sh # review before proceeding
|
||||
bash pi-hole-install.sh
|
||||
|
||||
# Follow the interactive installer:
|
||||
# 1. Select network interface (eth0 for wired — recommended)
|
||||
# 2. Select upstream DNS (Cloudflare or leave default — can change later)
|
||||
# 3. Confirm static IP
|
||||
# 4. Install the web admin interface (recommended)
|
||||
# 5. Note the admin password shown at the end
|
||||
# 1. Select network interface (eth0 for wired — recommended)
|
||||
# 2. Select upstream DNS (Cloudflare or leave default — can change later)
|
||||
# 3. Confirm static IP
|
||||
# 4. Install the web admin interface (recommended)
|
||||
# 5. Note the admin password shown at the end
|
||||
```
|
||||
|
||||
## Pointing Your Network at Pi-hole
|
||||
@@ -183,9 +183,9 @@ sudo systemctl start cloudflared
|
||||
sudo systemctl enable cloudflared
|
||||
|
||||
# Now point Pi-hole at the local DoH proxy:
|
||||
# Pi-hole admin → Settings → DNS → Custom upstream DNS
|
||||
# Set to: 127.0.0.1#5053
|
||||
# Uncheck all other upstream resolvers
|
||||
# Pi-hole admin → Settings → DNS → Custom upstream DNS
|
||||
# Set to: 127.0.0.1#5053
|
||||
# Uncheck all other upstream resolvers
|
||||
```
|
||||
|
||||
## Local DNS Records
|
||||
|
||||
@@ -218,8 +218,8 @@ stays reachable after an IP change.
|
||||
restart: unless-stopped
|
||||
|
||||
# ddns.env (chmod 600, not committed to git):
|
||||
# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
|
||||
# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
|
||||
# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
|
||||
# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
|
||||
|
||||
# Option 2: DuckDNS (free, simple)
|
||||
Sign up at duckdns.org → get a token and subdomain (myhome.duckdns.org)
|
||||
|
||||
@@ -28,7 +28,7 @@ origin: ECC
|
||||
@Path("/api/protected")
|
||||
@Authenticated
|
||||
public class ProtectedResource {
|
||||
|
||||
|
||||
@Inject
|
||||
JsonWebToken jwt;
|
||||
|
||||
@@ -65,20 +65,20 @@ quarkus.oidc.credentials.secret=${OIDC_SECRET}
|
||||
@Provider
|
||||
@Priority(Priorities.AUTHENTICATION)
|
||||
public class CustomAuthFilter implements ContainerRequestFilter {
|
||||
|
||||
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
|
||||
@Override
|
||||
public void filter(ContainerRequestContext requestContext) {
|
||||
String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
|
||||
|
||||
|
||||
// ヘッダーが無いまたは不正形式の場合は即座に拒否
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String token = authHeader.substring(7);
|
||||
if (!validateToken(token)) {
|
||||
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
|
||||
@@ -100,7 +100,7 @@ public class CustomAuthFilter implements ContainerRequestFilter {
|
||||
@Path("/api/admin")
|
||||
@RolesAllowed("ADMIN")
|
||||
public class AdminResource {
|
||||
|
||||
|
||||
@GET
|
||||
@Path("/users")
|
||||
public List<UserDto> listUsers() {
|
||||
@@ -118,7 +118,7 @@ public class AdminResource {
|
||||
|
||||
@Path("/api/users")
|
||||
public class UserResource {
|
||||
|
||||
|
||||
@Inject
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
@@ -127,7 +127,7 @@ public class UserResource {
|
||||
@RolesAllowed("USER")
|
||||
public Response getUser(@PathParam("id") Long id) {
|
||||
// 所有権確認
|
||||
if (!securityIdentity.hasRole("ADMIN") &&
|
||||
if (!securityIdentity.hasRole("ADMIN") &&
|
||||
!isOwner(id, securityIdentity.getPrincipal().getName())) {
|
||||
return Response.status(Response.Status.FORBIDDEN).build();
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public class UserResource {
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class SecurityService {
|
||||
|
||||
|
||||
@Inject
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
@@ -153,7 +153,7 @@ public class SecurityService {
|
||||
if (securityIdentity.isAnonymous()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (securityIdentity.hasRole("ADMIN")) {
|
||||
return true;
|
||||
}
|
||||
@@ -229,7 +229,7 @@ List<User> users = User.list("email = ?1 and active = ?2", email, true);
|
||||
Optional<User> user = User.find("username", username).firstResultOptional();
|
||||
|
||||
// 良い例:名前付きパラメータ
|
||||
List<User> users = User.list("email = :email and age > :minAge",
|
||||
List<User> users = User.list("email = :email and age > :minAge",
|
||||
Parameters.with("email", email).and("minAge", 18));
|
||||
```
|
||||
|
||||
@@ -256,7 +256,7 @@ public class User extends PanacheEntity {
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class PasswordService {
|
||||
|
||||
|
||||
public String hash(String plainPassword) {
|
||||
return BcryptUtil.bcryptHash(plainPassword);
|
||||
}
|
||||
@@ -324,7 +324,7 @@ quarkus.vault.authentication.kubernetes.role=my-role
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class SecretService {
|
||||
|
||||
|
||||
@ConfigProperty(name = "api-key")
|
||||
String apiKey; // Vault から取得
|
||||
|
||||
@@ -350,7 +350,7 @@ public class RateLimitFilter implements ContainerRequestFilter {
|
||||
@Override
|
||||
public void filter(ContainerRequestContext requestContext) {
|
||||
String clientId = getClientIdentifier();
|
||||
RateLimiter limiter = limiters.computeIfAbsent(clientId,
|
||||
RateLimiter limiter = limiters.computeIfAbsent(clientId,
|
||||
k -> RateLimiter.create(100.0)); // 1秒あたり100リクエスト
|
||||
|
||||
if (!limiter.tryAcquire()) {
|
||||
@@ -376,25 +376,25 @@ public class RateLimitFilter implements ContainerRequestFilter {
|
||||
```java
|
||||
@Provider
|
||||
public class SecurityHeadersFilter implements ContainerResponseFilter {
|
||||
|
||||
|
||||
@Override
|
||||
public void filter(ContainerRequestContext request, ContainerResponseContext response) {
|
||||
MultivaluedMap<String, Object> headers = response.getHeaders();
|
||||
|
||||
|
||||
// クリックジャッキング防止
|
||||
headers.putSingle("X-Frame-Options", "DENY");
|
||||
|
||||
|
||||
// XSS保護
|
||||
headers.putSingle("X-Content-Type-Options", "nosniff");
|
||||
headers.putSingle("X-XSS-Protection", "1; mode=block");
|
||||
|
||||
|
||||
// HSTS
|
||||
headers.putSingle("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
||||
|
||||
|
||||
// CSP — script-src用の'unsafe-inline'は避けてください。XSS保護を無効化します。
|
||||
// 代わりにnoncesまたはhashesを使用します。CSSフレームワークが必要な場合、
|
||||
// style-srcの'unsafe-inline'は許容ですが、可能な場合はnoncesを優先してください。
|
||||
headers.putSingle("Content-Security-Policy",
|
||||
headers.putSingle("Content-Security-Policy",
|
||||
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
|
||||
}
|
||||
}
|
||||
@@ -411,11 +411,11 @@ public class AuditService {
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
public void logAccess(String resource, String action) {
|
||||
String user = securityIdentity.isAnonymous()
|
||||
? "anonymous"
|
||||
String user = securityIdentity.isAnonymous()
|
||||
? "anonymous"
|
||||
: securityIdentity.getPrincipal().getName();
|
||||
|
||||
LOG.infof("AUDIT: user=%s action=%s resource=%s timestamp=%s",
|
||||
|
||||
LOG.infof("AUDIT: user=%s action=%s resource=%s timestamp=%s",
|
||||
user, action, resource, Instant.now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,19 +34,19 @@ origin: ECC
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("OrderService Unit Tests")
|
||||
class OrderServiceTest {
|
||||
|
||||
|
||||
@Mock
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
|
||||
@Mock
|
||||
private EventService eventService;
|
||||
|
||||
|
||||
@Mock
|
||||
private FulfillmentPublisher fulfillmentPublisher;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private OrderService orderService;
|
||||
|
||||
|
||||
private CreateOrderCommand validCommand;
|
||||
|
||||
@BeforeEach
|
||||
@@ -60,16 +60,16 @@ class OrderServiceTest {
|
||||
@Nested
|
||||
@DisplayName("createOrder のテスト")
|
||||
class CreateOrder {
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("有効なコマンドが与えられた場合、注文を永続化してフルフィルメントイベントを発行する")
|
||||
void givenValidCommand_whenCreateOrder_thenPersistsAndPublishes() {
|
||||
// ARRANGE
|
||||
doNothing().when(orderRepository).persist(any(Order.class));
|
||||
|
||||
|
||||
// ACT
|
||||
OrderReceipt receipt = orderService.createOrder(validCommand);
|
||||
|
||||
|
||||
// ASSERT
|
||||
assertThat(receipt).isNotNull();
|
||||
assertThat(receipt.customerId()).isEqualTo("customer-123");
|
||||
@@ -83,7 +83,7 @@ class OrderServiceTest {
|
||||
void givenMissingCustomerId_whenCreateOrder_thenThrowsBadRequest() {
|
||||
// ARRANGE
|
||||
CreateOrderCommand invalid = new CreateOrderCommand("", validCommand.lines());
|
||||
|
||||
|
||||
// ACT & ASSERT
|
||||
WebApplicationException exception = assertThrows(
|
||||
WebApplicationException.class,
|
||||
@@ -101,13 +101,13 @@ class OrderServiceTest {
|
||||
// ARRANGE
|
||||
doThrow(new PersistenceException("database unavailable"))
|
||||
.when(orderRepository).persist(any(Order.class));
|
||||
|
||||
|
||||
// ACT & ASSERT
|
||||
PersistenceException exception = assertThrows(
|
||||
PersistenceException.class,
|
||||
() -> orderService.createOrder(validCommand)
|
||||
);
|
||||
|
||||
|
||||
assertThat(exception.getMessage()).contains("database unavailable");
|
||||
verify(eventService).createErrorEvent(
|
||||
eq(validCommand),
|
||||
@@ -125,7 +125,7 @@ class OrderServiceTest {
|
||||
NullPointerException.class,
|
||||
() -> orderService.createOrder(null)
|
||||
);
|
||||
|
||||
|
||||
verify(orderRepository, never()).persist(any(Order.class));
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ class BusinessRulesRouteTest {
|
||||
// ARRANGE
|
||||
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
|
||||
mockRabbitMQ.expectedMessageCount(1);
|
||||
|
||||
|
||||
// テスト用の実エンドポイントをモックに置き換え
|
||||
camelContext.getRouteController().stopRoute("business-rules-publisher");
|
||||
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
|
||||
@@ -192,13 +192,13 @@ class BusinessRulesRouteTest {
|
||||
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
|
||||
});
|
||||
camelContext.getRouteController().startRoute("business-rules-publisher");
|
||||
|
||||
|
||||
// ACT
|
||||
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
|
||||
|
||||
|
||||
// ASSERT — .marshal().json(JsonLibrary.Jackson)の後、bodyはJSON文字列
|
||||
mockRabbitMQ.assertIsSatisfied(5000);
|
||||
|
||||
|
||||
assertThat(mockRabbitMQ.getExchanges()).hasSize(1);
|
||||
String body = mockRabbitMQ.getExchanges().get(0).getIn().getBody(String.class);
|
||||
assertThat(body).contains("\"documentId\":1");
|
||||
@@ -211,19 +211,19 @@ class BusinessRulesRouteTest {
|
||||
MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");
|
||||
camelContext.addEndpoint("mock:marshal", mockMarshal);
|
||||
mockMarshal.expectedMessageCount(1);
|
||||
|
||||
|
||||
camelContext.getRouteController().stopRoute("business-rules-publisher");
|
||||
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
|
||||
advice.weaveAddLast().to("mock:marshal");
|
||||
});
|
||||
camelContext.getRouteController().startRoute("business-rules-publisher");
|
||||
|
||||
|
||||
// ACT
|
||||
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
|
||||
|
||||
|
||||
// ASSERT
|
||||
mockMarshal.assertIsSatisfied(5000);
|
||||
|
||||
|
||||
String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);
|
||||
assertThat(body).contains("\"documentId\":1");
|
||||
assertThat(body).contains("\"flowProfile\":\"BASIC\"");
|
||||
@@ -240,17 +240,17 @@ class BusinessRulesRouteTest {
|
||||
// ARRANGE
|
||||
MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);
|
||||
mockInvoice.expectedMessageCount(1);
|
||||
|
||||
|
||||
camelContext.getRouteController().stopRoute("document-processing");
|
||||
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
|
||||
advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");
|
||||
});
|
||||
camelContext.getRouteController().startRoute("document-processing");
|
||||
|
||||
|
||||
// ACT
|
||||
producerTemplate.sendBodyAndHeader("direct:process-document",
|
||||
producerTemplate.sendBodyAndHeader("direct:process-document",
|
||||
testPayload, "documentType", "INVOICE");
|
||||
|
||||
|
||||
// ASSERT
|
||||
mockInvoice.assertIsSatisfied(5000);
|
||||
}
|
||||
@@ -261,23 +261,23 @@ class BusinessRulesRouteTest {
|
||||
// ARRANGE
|
||||
MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);
|
||||
mockError.expectedMessageCount(1);
|
||||
|
||||
|
||||
camelContext.getRouteController().stopRoute("document-processing");
|
||||
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
|
||||
advice.weaveByToString(".*direct:validation-error-handler.*")
|
||||
.replace().to("mock:error");
|
||||
});
|
||||
camelContext.getRouteController().startRoute("document-processing");
|
||||
|
||||
|
||||
// バリデータビーンをモック化して例外をスロー
|
||||
when(documentValidator.validate(any())).thenThrow(new ValidationException("Invalid document"));
|
||||
|
||||
|
||||
// ACT
|
||||
producerTemplate.sendBody("direct:process-document", testPayload);
|
||||
|
||||
|
||||
// ASSERT
|
||||
mockError.assertIsSatisfied(5000);
|
||||
|
||||
|
||||
Exception exception = mockError.getExchanges().get(0).getException();
|
||||
assertThat(exception).isInstanceOf(ValidationException.class);
|
||||
assertThat(exception.getMessage()).contains("Invalid document");
|
||||
@@ -295,13 +295,13 @@ class EventServiceTest {
|
||||
|
||||
@Mock
|
||||
private EventRepository eventRepository;
|
||||
|
||||
|
||||
@Mock
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private EventService eventService;
|
||||
|
||||
|
||||
private BusinessRulesPayload testPayload;
|
||||
|
||||
@BeforeEach
|
||||
@@ -314,19 +314,19 @@ class EventServiceTest {
|
||||
@Nested
|
||||
@DisplayName("createSuccessEvent のテスト")
|
||||
class CreateSuccessEvent {
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("有効なペイロードが与えられた場合、正しい属性でサクセスイベント作成")
|
||||
void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
|
||||
// ARRANGE
|
||||
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
|
||||
|
||||
|
||||
// ACT
|
||||
assertDoesNotThrow(() ->
|
||||
assertDoesNotThrow(() ->
|
||||
eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));
|
||||
|
||||
|
||||
// ASSERT
|
||||
verify(eventRepository).persist(argThat(event ->
|
||||
verify(eventRepository).persist(argThat(event ->
|
||||
event.getType().equals("DOCUMENT_PROCESSED") &&
|
||||
event.getStatus() == EventStatus.SUCCESS &&
|
||||
event.getPayload().equals("{\"documentId\":1}") &&
|
||||
@@ -339,13 +339,13 @@ class EventServiceTest {
|
||||
void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
|
||||
// ARRANGE
|
||||
Object nullPayload = null;
|
||||
|
||||
|
||||
// ACT & ASSERT
|
||||
NullPointerException exception = assertThrows(
|
||||
NullPointerException.class,
|
||||
() -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")
|
||||
);
|
||||
|
||||
|
||||
assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");
|
||||
verify(eventRepository, never()).persist(any());
|
||||
}
|
||||
@@ -354,20 +354,20 @@ class EventServiceTest {
|
||||
@Nested
|
||||
@DisplayName("createErrorEvent のテスト")
|
||||
class CreateErrorEvent {
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("エラーが与えられた場合、エラーメッセージ付きエラーイベント作成")
|
||||
void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {
|
||||
// ARRANGE
|
||||
String errorMessage = "Processing failed";
|
||||
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
|
||||
|
||||
|
||||
// ACT
|
||||
assertDoesNotThrow(() ->
|
||||
assertDoesNotThrow(() ->
|
||||
eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
|
||||
|
||||
|
||||
// ASSERT
|
||||
verify(eventRepository).persist(argThat(event ->
|
||||
verify(eventRepository).persist(argThat(event ->
|
||||
event.getType().equals("PROCESSING_ERROR") &&
|
||||
event.getStatus() == EventStatus.ERROR &&
|
||||
event.getErrorMessage().equals(errorMessage) &&
|
||||
@@ -384,7 +384,7 @@ class EventServiceTest {
|
||||
IllegalArgumentException.class,
|
||||
() -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)
|
||||
);
|
||||
|
||||
|
||||
assertThat(exception.getMessage()).contains("Error message cannot be blank");
|
||||
}
|
||||
}
|
||||
@@ -400,13 +400,13 @@ class FileStorageServiceTest {
|
||||
|
||||
@Mock
|
||||
private S3Client s3Client;
|
||||
|
||||
|
||||
@Mock
|
||||
private ExecutorService executorService;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private FileStorageService fileStorageService;
|
||||
|
||||
|
||||
private InputStream testInputStream;
|
||||
private LogContext testLogContext;
|
||||
|
||||
@@ -421,7 +421,7 @@ class FileStorageServiceTest {
|
||||
@Nested
|
||||
@DisplayName("uploadOriginalFile のテスト")
|
||||
class UploadOriginalFile {
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("有効なファイルが与えられた場合、ファイルアップロード成功とドキュメント情報を返す")
|
||||
void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {
|
||||
@@ -430,23 +430,23 @@ class FileStorageServiceTest {
|
||||
((Runnable) invocation.getArgument(0)).run();
|
||||
return null;
|
||||
}).when(executorService).execute(any(Runnable.class));
|
||||
|
||||
|
||||
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
|
||||
.thenReturn(PutObjectResponse.builder().build());
|
||||
|
||||
|
||||
// ACT
|
||||
CompletableFuture<StoredDocumentInfo> future =
|
||||
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
||||
CompletableFuture<StoredDocumentInfo> future =
|
||||
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
||||
testLogContext, InvoiceFormat.UBL);
|
||||
|
||||
|
||||
StoredDocumentInfo result = future.join();
|
||||
|
||||
|
||||
// ASSERT
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPath()).isNotBlank();
|
||||
assertThat(result.getSize()).isEqualTo(1024L);
|
||||
assertThat(result.getUploadedAt()).isNotNull();
|
||||
|
||||
|
||||
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
|
||||
}
|
||||
|
||||
@@ -458,15 +458,15 @@ class FileStorageServiceTest {
|
||||
((Runnable) invocation.getArgument(0)).run();
|
||||
return null;
|
||||
}).when(executorService).execute(any(Runnable.class));
|
||||
|
||||
|
||||
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
|
||||
.thenThrow(new StorageException("S3 unavailable"));
|
||||
|
||||
|
||||
// ACT
|
||||
CompletableFuture<StoredDocumentInfo> future =
|
||||
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
||||
CompletableFuture<StoredDocumentInfo> future =
|
||||
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
||||
testLogContext, InvoiceFormat.UBL);
|
||||
|
||||
|
||||
// ASSERT
|
||||
assertThatThrownBy(() -> future.join())
|
||||
.isInstanceOf(CompletionException.class)
|
||||
@@ -479,17 +479,17 @@ class FileStorageServiceTest {
|
||||
void givenLogContext_whenUpload_thenContextPropagated() throws Exception {
|
||||
// ARRANGE
|
||||
AtomicReference<LogContext> capturedContext = new AtomicReference<>();
|
||||
|
||||
|
||||
doAnswer(invocation -> {
|
||||
capturedContext.set(CustomLog.getCurrentContext());
|
||||
((Runnable) invocation.getArgument(0)).run();
|
||||
return null;
|
||||
}).when(executorService).execute(any(Runnable.class));
|
||||
|
||||
|
||||
// ACT
|
||||
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
||||
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
||||
testLogContext, InvoiceFormat.UBL).join();
|
||||
|
||||
|
||||
// ASSERT
|
||||
assertThat(capturedContext.get()).isNotNull();
|
||||
assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");
|
||||
@@ -641,7 +641,7 @@ class DocumentIntegrationTest {
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
|
||||
<!-- カバレッジレポート生成 -->
|
||||
<execution>
|
||||
<id>report</id>
|
||||
@@ -650,7 +650,7 @@ class DocumentIntegrationTest {
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
|
||||
<!-- カバレッジ閾値を強制 -->
|
||||
<execution>
|
||||
<id>check</id>
|
||||
@@ -705,14 +705,14 @@ mvn jacoco:check
|
||||
<artifactId>quarkus-junit5-mockito</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Mockito -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- AssertJ(JUnitアサーション推奨) -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
@@ -720,14 +720,14 @@ mvn jacoco:check
|
||||
<version>3.24.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- REST Assured -->
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Camel Testing -->
|
||||
<dependency>
|
||||
<groupId>org.apache.camel.quarkus</groupId>
|
||||
|
||||
@@ -437,28 +437,28 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
|
||||
|
||||
- name: Build
|
||||
run: mvn clean verify -DskipTests
|
||||
|
||||
|
||||
- name: Test with Coverage
|
||||
run: mvn test jacoco:report jacoco:check
|
||||
|
||||
|
||||
- name: Security Scan
|
||||
run: mvn org.owasp:dependency-check-maven:check
|
||||
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
|
||||
Reference in New Issue
Block a user