test(security): cover scan task after-commit publishing

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-08-21 10:28:15 +08:00
parent caf457f299
commit eef9b4a76e

View file

@ -5,21 +5,26 @@ import com.iflytek.skillhub.domain.skill.SkillVersion;
import com.iflytek.skillhub.domain.skill.SkillVersionRepository;
import com.iflytek.skillhub.domain.skill.SkillVersionStatus;
import com.iflytek.skillhub.domain.skill.validation.PackageEntry;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
@ -48,6 +53,13 @@ class SecurityScanServiceTest {
);
}
@AfterEach
void clearTransactionSynchronization() {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.clearSynchronization();
}
}
@Test
void securityAudit_startsWithSuspiciousUnsafeDefaults() {
SecurityAudit audit = new SecurityAudit(42L, ScannerType.SKILL_SCANNER);
@ -124,6 +136,54 @@ class SecurityScanServiceTest {
assertThat(task.bundleKey()).isEqualTo("packages/8/42/bundle.zip");
}
@Test
void triggerScan_defersTaskPublishingUntilTransactionCommit() throws Exception {
SkillVersion version = new SkillVersion(8L, "1.0.0", "publisher-1");
setId(version, 42L);
PackageEntry entry = new PackageEntry(
"README.md",
"# demo".getBytes(),
6L,
"text/markdown"
);
given(skillVersionRepository.findById(42L)).willReturn(Optional.of(version));
TransactionSynchronizationManager.initSynchronization();
service.triggerScan(42L, List.of(entry), "publisher-1");
verify(auditRepository).save(any(SecurityAudit.class));
verify(skillVersionRepository).save(version);
verify(scanTaskProducer, never()).publishScanTask(any(ScanTask.class));
assertThat(version.getStatus()).isEqualTo(SkillVersionStatus.SCANNING);
commitRegisteredSynchronizations();
ArgumentCaptor<ScanTask> taskCaptor = ArgumentCaptor.forClass(ScanTask.class);
verify(scanTaskProducer).publishScanTask(taskCaptor.capture());
assertThat(taskCaptor.getValue().versionId()).isEqualTo(42L);
}
@Test
void triggerScan_doesNotPublishTaskWhenTransactionNeverCommits() throws Exception {
SkillVersion version = new SkillVersion(8L, "1.0.0", "publisher-1");
setId(version, 42L);
PackageEntry entry = new PackageEntry(
"README.md",
"# demo".getBytes(),
6L,
"text/markdown"
);
given(skillVersionRepository.findById(42L)).willReturn(Optional.of(version));
TransactionSynchronizationManager.initSynchronization();
service.triggerScan(42L, List.of(entry), "publisher-1");
verify(auditRepository).save(any(SecurityAudit.class));
verify(scanTaskProducer, never()).publishScanTask(any(ScanTask.class));
}
@Test
void triggerScan_rejectsDirectoryTraversalEntries() throws Exception {
SkillVersion version = new SkillVersion(8L, "1.0.0", "publisher-1");
@ -264,4 +324,10 @@ class SecurityScanServiceTest {
field.setAccessible(true);
field.set(target, id);
}
private void commitRegisteredSynchronizations() {
for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
synchronization.afterCommit();
}
}
}