mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 18:10:30 +08:00
feat(platform): I1 bootstrap, I2 M1 APIs, OpenAPI SSOT, and CI guards
Deliver dual Spring Boot services (platform API + webhook ingress), JWT auth, Flyway with isolated history tables, customer/project/dictionary endpoints, OpenAPI snapshot under contracts/, RUNBOOK, and CI that runs on services/web/contracts paths plus enforcer + dependency tree ban on craftlabs-auth-bitanswer. Made-with: Cursor
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
||||
package cn.craftlabs.platform.webhook;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class CallbackIngestControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void rejectsWithoutToken() throws Exception {
|
||||
mockMvc.perform(
|
||||
post("/webhook/bitanswer/callback")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{}"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsWithToken() throws Exception {
|
||||
mockMvc.perform(
|
||||
post("/webhook/bitanswer/callback")
|
||||
.header(CallbackIngestController.HEADER_TOKEN, "test-secret")
|
||||
.header("Idempotency-Key", "k1")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"event\":\"sn:post_activate\"}"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateIdempotencyKeyStillOk() throws Exception {
|
||||
String body = "{\"event\":\"dup\"}";
|
||||
for (int i = 0; i < 2; i++) {
|
||||
mockMvc.perform(
|
||||
post("/webhook/bitanswer/callback")
|
||||
.header(CallbackIngestController.HEADER_TOKEN, "test-secret")
|
||||
.header("Idempotency-Key", "stable-key")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:webhook_ingress;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
flyway:
|
||||
enabled: false
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
schema-locations: classpath:schema-webhook-test.sql
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
|
||||
craftlabs:
|
||||
webhook:
|
||||
expected-token: test-secret
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 单测建表(与 db/migration/V1 语义一致);单测关闭 Flyway,由 spring.sql.init 执行
|
||||
CREATE TABLE IF NOT EXISTS webhook_callback_receipt (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
idempotency_key VARCHAR(512),
|
||||
body_bytes INT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_webhook_idempotency UNIQUE (idempotency_key)
|
||||
);
|
||||
Reference in New Issue
Block a user