mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-28 11:25:00 +00:00
chore(auth): sync binding contract gate
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
commit
c6e390f643
7 changed files with 153 additions and 6 deletions
|
|
@ -10,15 +10,29 @@ POSTGRES_PASSWORD="identity-v2-test-password"
|
|||
POSTGRES_DB="identity_v2"
|
||||
MAVEN_CACHE_DIR="${MAVEN_CACHE_DIR:-${HOME}/.m2}"
|
||||
|
||||
log() {
|
||||
printf '[identity-binding-v2] %s\n' "$*"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
exit_code="$?"
|
||||
if [[ "${exit_code}" -ne 0 ]]; then
|
||||
log "failed with exit code ${exit_code}"
|
||||
docker ps -a \
|
||||
--filter "label=skillhub.test.run=${RUN_ID}" \
|
||||
--format 'resource={{.Names}} status={{.Status}}' || true
|
||||
docker logs "${POSTGRES_CONTAINER}" 2>&1 || true
|
||||
fi
|
||||
docker rm -f "${POSTGRES_CONTAINER}" >/dev/null 2>&1 || true
|
||||
docker network rm "${NETWORK}" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
log "creating isolated Docker network ${NETWORK}"
|
||||
docker network create \
|
||||
--label "skillhub.test.run=${RUN_ID}" \
|
||||
"${NETWORK}" >/dev/null
|
||||
log "starting isolated PostgreSQL container ${POSTGRES_CONTAINER}"
|
||||
docker run -d \
|
||||
--name "${POSTGRES_CONTAINER}" \
|
||||
--label "skillhub.test.run=${RUN_ID}" \
|
||||
|
|
@ -31,6 +45,7 @@ docker run -d \
|
|||
-p 127.0.0.1::5432 \
|
||||
postgres:16-alpine >/dev/null
|
||||
|
||||
log "waiting for PostgreSQL readiness"
|
||||
for _ in $(seq 1 60); do
|
||||
if docker exec "${POSTGRES_CONTAINER}" \
|
||||
pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \
|
||||
|
|
@ -42,17 +57,23 @@ done
|
|||
docker exec "${POSTGRES_CONTAINER}" \
|
||||
pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \
|
||||
>/dev/null
|
||||
log "PostgreSQL is ready"
|
||||
|
||||
run_test() {
|
||||
test_class="$1"
|
||||
flyway_target="${2:-}"
|
||||
java_version=""
|
||||
if command -v java >/dev/null 2>&1; then
|
||||
java_version="$(java -version 2>&1 | head -n 1)"
|
||||
java_version="$(java -version 2>&1)"
|
||||
fi
|
||||
if [[ "${java_version}" == *'"21.'* ]]; then
|
||||
host_port="$(docker port "${POSTGRES_CONTAINER}" 5432/tcp \
|
||||
| sed -n 's/.*://p')"
|
||||
if [[ -z "${host_port}" ]]; then
|
||||
log "Docker did not publish a PostgreSQL host port"
|
||||
return 1
|
||||
fi
|
||||
log "running ${test_class} with host Java 21"
|
||||
(
|
||||
cd "${REPO_ROOT}/server"
|
||||
IDENTITY_BINDING_V2_POSTGRES_URL="jdbc:postgresql://127.0.0.1:${host_port}/${POSTGRES_DB}" \
|
||||
|
|
@ -70,6 +91,7 @@ run_test() {
|
|||
return
|
||||
fi
|
||||
|
||||
log "running ${test_class} with containerized Java 21"
|
||||
mkdir -p "${MAVEN_CACHE_DIR}"
|
||||
docker run --rm \
|
||||
--name "${RUN_ID}-java" \
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package com.iflytek.skillhub.config;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* Enables asynchronous event handling and other background execution features used by the
|
||||
* application module.
|
||||
|
|
@ -26,7 +28,30 @@ public class AsyncConfig {
|
|||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("skillhub-event-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.setTaskDecorator(mdcTaskDecorator());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
private TaskDecorator mdcTaskDecorator() {
|
||||
return task -> {
|
||||
Map<String, String> callerContext = MDC.getCopyOfContextMap();
|
||||
return () -> {
|
||||
Map<String, String> executorContext = MDC.getCopyOfContextMap();
|
||||
try {
|
||||
restoreMdc(callerContext);
|
||||
task.run();
|
||||
} finally {
|
||||
restoreMdc(executorContext);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private void restoreMdc(Map<String, String> context) {
|
||||
MDC.clear();
|
||||
if (context != null) {
|
||||
MDC.setContextMap(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,4 +60,8 @@ public class SkillHubMetrics {
|
|||
"operation", operation
|
||||
).increment();
|
||||
}
|
||||
|
||||
public void incrementSearchRebuildFailure() {
|
||||
meterRegistry.counter("skillhub.search.rebuild.failure").increment();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.iflytek.skillhub.service;
|
||||
|
||||
import com.iflytek.skillhub.metrics.SkillHubMetrics;
|
||||
import com.iflytek.skillhub.search.SearchRebuildService;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -15,9 +16,12 @@ public class LabelSearchSyncService {
|
|||
private static final Logger log = LoggerFactory.getLogger(LabelSearchSyncService.class);
|
||||
|
||||
private final SearchRebuildService searchRebuildService;
|
||||
private final SkillHubMetrics metrics;
|
||||
|
||||
public LabelSearchSyncService(SearchRebuildService searchRebuildService) {
|
||||
public LabelSearchSyncService(SearchRebuildService searchRebuildService,
|
||||
SkillHubMetrics metrics) {
|
||||
this.searchRebuildService = searchRebuildService;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Async("skillhubEventExecutor")
|
||||
|
|
@ -25,6 +29,7 @@ public class LabelSearchSyncService {
|
|||
try {
|
||||
searchRebuildService.rebuildBySkill(skillId);
|
||||
} catch (RuntimeException ex) {
|
||||
metrics.incrementSearchRebuildFailure();
|
||||
log.error("Failed to rebuild search document for skill {}", skillId, ex);
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +46,7 @@ public class LabelSearchSyncService {
|
|||
try {
|
||||
searchRebuildService.rebuildBySkill(skillId);
|
||||
} catch (RuntimeException ex) {
|
||||
metrics.incrementSearchRebuildFailure();
|
||||
log.error("Failed to rebuild search document for skill {} after label change", skillId, ex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ package com.iflytek.skillhub.config;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
class AsyncConfigTest {
|
||||
|
||||
|
|
@ -13,4 +17,26 @@ class AsyncConfigTest {
|
|||
assertThat(AsyncConfig.class).hasAnnotation(EnableAsync.class);
|
||||
assertThat(AsyncConfig.class).hasAnnotation(EnableScheduling.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skillhubEventExecutor_propagatesAndClearsMdc() throws Exception {
|
||||
ThreadPoolTaskExecutor executor =
|
||||
(ThreadPoolTaskExecutor) new AsyncConfig().skillhubEventExecutor();
|
||||
try {
|
||||
MDC.put("requestId", "req-597");
|
||||
CompletableFuture<String> propagatedRequestId = new CompletableFuture<>();
|
||||
executor.execute(() -> propagatedRequestId.complete(MDC.get("requestId")));
|
||||
MDC.clear();
|
||||
|
||||
assertThat(propagatedRequestId.get(5, TimeUnit.SECONDS)).isEqualTo("req-597");
|
||||
|
||||
CompletableFuture<String> nextRequestId = new CompletableFuture<>();
|
||||
executor.execute(() -> nextRequestId.complete(MDC.get("requestId")));
|
||||
|
||||
assertThat(nextRequestId.get(5, TimeUnit.SECONDS)).isNull();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class PrometheusEndpointTest {
|
|||
skillHubMetrics.incrementUserRegister();
|
||||
skillHubMetrics.recordLocalLogin(true);
|
||||
skillHubMetrics.incrementSkillPublish("global", "PENDING_REVIEW");
|
||||
skillHubMetrics.incrementSearchRebuildFailure();
|
||||
|
||||
assertThat(environment.getProperty("management.endpoints.web.exposure.include"))
|
||||
.doesNotContain("prometheus")
|
||||
|
|
@ -54,5 +55,8 @@ class PrometheusEndpointTest {
|
|||
.tag("status", "PENDING_REVIEW")
|
||||
.counter()
|
||||
.count()).isEqualTo(1.0d);
|
||||
assertThat(meterRegistry.get("skillhub.search.rebuild.failure")
|
||||
.counter()
|
||||
.count()).isEqualTo(1.0d);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
package com.iflytek.skillhub.service;
|
||||
|
||||
import com.iflytek.skillhub.metrics.SkillHubMetrics;
|
||||
import com.iflytek.skillhub.search.SearchRebuildService;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
class LabelSearchSyncServiceTest {
|
||||
|
|
@ -15,7 +21,8 @@ class LabelSearchSyncServiceTest {
|
|||
@Test
|
||||
void rebuildSkillsShouldSkipNullsAndDuplicatesWhileProcessingLargeLists() {
|
||||
SearchRebuildService rebuildService = mock(SearchRebuildService.class);
|
||||
LabelSearchSyncService service = new LabelSearchSyncService(rebuildService);
|
||||
SkillHubMetrics metrics = mock(SkillHubMetrics.class);
|
||||
LabelSearchSyncService service = new LabelSearchSyncService(rebuildService, metrics);
|
||||
List<Long> skillIds = new ArrayList<>();
|
||||
skillIds.add(null);
|
||||
for (long i = 1; i <= 120; i++) {
|
||||
|
|
@ -30,5 +37,58 @@ class LabelSearchSyncServiceTest {
|
|||
verify(rebuildService).rebuildBySkill(i);
|
||||
}
|
||||
verifyNoMoreInteractions(rebuildService);
|
||||
verifyNoInteractions(metrics);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rebuildSkillFailureShouldIncrementMetric() {
|
||||
SearchRebuildService rebuildService = mock(SearchRebuildService.class);
|
||||
doThrow(new IllegalStateException("search unavailable"))
|
||||
.when(rebuildService)
|
||||
.rebuildBySkill(42L);
|
||||
|
||||
contextRunner(rebuildService).run(context -> {
|
||||
LabelSearchSyncService service = context.getBean(LabelSearchSyncService.class);
|
||||
SimpleMeterRegistry meterRegistry = context.getBean(SimpleMeterRegistry.class);
|
||||
|
||||
service.rebuildSkill(42L);
|
||||
|
||||
assertThat(meterRegistry.get("skillhub.search.rebuild.failure").counter().count())
|
||||
.isEqualTo(1.0d);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void rebuildSkillsShouldCountEachFailureAndContinue() {
|
||||
SearchRebuildService rebuildService = mock(SearchRebuildService.class);
|
||||
doThrow(new IllegalStateException("search unavailable"))
|
||||
.when(rebuildService)
|
||||
.rebuildBySkill(2L);
|
||||
doThrow(new IllegalStateException("search unavailable"))
|
||||
.when(rebuildService)
|
||||
.rebuildBySkill(3L);
|
||||
|
||||
contextRunner(rebuildService).run(context -> {
|
||||
LabelSearchSyncService service = context.getBean(LabelSearchSyncService.class);
|
||||
SimpleMeterRegistry meterRegistry = context.getBean(SimpleMeterRegistry.class);
|
||||
|
||||
service.rebuildSkills(List.of(1L, 2L, 3L, 4L));
|
||||
|
||||
assertThat(meterRegistry.get("skillhub.search.rebuild.failure").counter().count())
|
||||
.isEqualTo(2.0d);
|
||||
verify(rebuildService).rebuildBySkill(1L);
|
||||
verify(rebuildService).rebuildBySkill(2L);
|
||||
verify(rebuildService).rebuildBySkill(3L);
|
||||
verify(rebuildService).rebuildBySkill(4L);
|
||||
verifyNoMoreInteractions(rebuildService);
|
||||
});
|
||||
}
|
||||
|
||||
private ApplicationContextRunner contextRunner(SearchRebuildService rebuildService) {
|
||||
return new ApplicationContextRunner()
|
||||
.withBean(SearchRebuildService.class, () -> rebuildService)
|
||||
.withBean(SimpleMeterRegistry.class)
|
||||
.withBean(SkillHubMetrics.class)
|
||||
.withBean(LabelSearchSyncService.class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue