feat: 重构标准 Skill 管理与发布链路
- 统一标准 ZIP 导入导出与通用资源模型 - 收口分类范围权限和创建人查询 - 完善发布快照、审批幂等与数据库清理迁移
This commit is contained in:
@@ -153,8 +153,8 @@ public class SkillFileServiceImplTransactionTest {
|
||||
|
||||
SkillResource resource = savedResource.get();
|
||||
assertTrue(resource.getIsText());
|
||||
assertEquals("SCRIPT", resource.getKind());
|
||||
assertEquals("PYTHON", resource.getLanguage());
|
||||
assertEquals("SCRIPT", result.getType());
|
||||
assertEquals("PYTHON", result.getLanguage());
|
||||
assertEquals("print('ok')\n", resource.getTextContent());
|
||||
assertNull(resource.getContentRef());
|
||||
assertTrue(result.getIsText());
|
||||
@@ -177,7 +177,7 @@ public class SkillFileServiceImplTransactionTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证二进制资源重命名到 scripts 后转为文本,并释放原内容引用。
|
||||
* 验证二进制资源改为文本扩展名后转为文本,并释放原内容引用。
|
||||
*/
|
||||
@Test
|
||||
public void binaryRenameToScriptConvertsAndReleasesContent() {
|
||||
@@ -197,8 +197,8 @@ public class SkillFileServiceImplTransactionTest {
|
||||
|
||||
assertTrue(resource.getIsText());
|
||||
assertEquals("scripts/tool.py", resource.getNormalizedPath());
|
||||
assertEquals("SCRIPT", resource.getKind());
|
||||
assertEquals("PYTHON", resource.getLanguage());
|
||||
assertEquals("SCRIPT", result.getType());
|
||||
assertEquals("PYTHON", result.getLanguage());
|
||||
assertNull(resource.getContentRef());
|
||||
assertEquals("print('ok')\n", resource.getTextContent());
|
||||
assertTrue(result.getIsText());
|
||||
@@ -206,45 +206,82 @@ public class SkillFileServiceImplTransactionTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文本资源重命名到 assets 后转为二进制内容引用。
|
||||
* 验证文本资源移动到 assets 后仍按扩展名保留文本表示。
|
||||
*/
|
||||
@Test
|
||||
public void textRenameToAssetConvertsToBinaryRepresentation() {
|
||||
public void textRenameToAssetKeepsTextRepresentation() {
|
||||
String sourceHash = "c".repeat(64);
|
||||
SkillResource resource = resource(
|
||||
"references/guide.md", true, "# Guide\n", null, sourceHash, 8L);
|
||||
when(skillResourceService.list(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(), List.of(resource), List.of(resource), List.of(resource));
|
||||
when(skillResourceService.update(eq(resource), any(QueryWrapper.class))).thenReturn(true);
|
||||
when(contentStore.put(any(byte[].class))).thenReturn(NEW_CONTENT_REF);
|
||||
SkillFileRenameRequest request = renameRequest(
|
||||
"references/guide.md", "assets/guide.md", sourceHash);
|
||||
|
||||
SkillFileContent result = service.renameFile(request);
|
||||
|
||||
assertFalse(resource.getIsText());
|
||||
assertEquals("ASSET", resource.getKind());
|
||||
assertEquals(NEW_CONTENT_REF, resource.getContentRef());
|
||||
assertNull(resource.getTextContent());
|
||||
assertFalse(result.getIsText());
|
||||
verify(contentStore).put("# Guide\n".getBytes(StandardCharsets.UTF_8));
|
||||
assertTrue(resource.getIsText());
|
||||
assertEquals("ASSET", result.getType());
|
||||
assertNull(resource.getContentRef());
|
||||
assertEquals("# Guide\n", resource.getTextContent());
|
||||
assertTrue(result.getIsText());
|
||||
verify(contentStore, never()).put(any(byte[].class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 assets 路径不能通过文本创建入口形成非规范表示。
|
||||
* 验证 assets 目录允许创建可编辑文本资源。
|
||||
*/
|
||||
@Test
|
||||
public void createTextAssetIsRejected() {
|
||||
public void createTextAssetUsesCanonicalTextRepresentation() {
|
||||
AtomicReference<SkillResource> savedResource = new AtomicReference<>();
|
||||
when(skillResourceService.list(any(QueryWrapper.class)))
|
||||
.thenAnswer(invocation -> savedResource.get() == null
|
||||
? List.of() : List.of(savedResource.get()));
|
||||
when(skillResourceService.save(any(SkillResource.class))).thenAnswer(invocation -> {
|
||||
SkillResource resource = invocation.getArgument(0);
|
||||
resource.setId(BigInteger.valueOf(11));
|
||||
savedResource.set(resource);
|
||||
return true;
|
||||
});
|
||||
SkillFileSaveRequest request = new SkillFileSaveRequest();
|
||||
request.setSkillId(SKILL_ID);
|
||||
request.setPath("assets/readme.txt");
|
||||
request.setContent("text");
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class,
|
||||
() -> service.createTextFile(request));
|
||||
SkillFileContent result = service.createTextFile(request);
|
||||
|
||||
assertTrue(exception.getMessage().contains("二进制文件管理"));
|
||||
verify(skillResourceService, never()).save(any(SkillResource.class));
|
||||
assertEquals("ASSET", result.getType());
|
||||
assertTrue(result.getIsText());
|
||||
assertEquals("text", savedResource.get().getTextContent());
|
||||
assertNull(savedResource.get().getContentRef());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 scripts 目录中的不透明文件按二进制资源无损保存。
|
||||
*/
|
||||
@Test
|
||||
public void binaryScriptUploadUsesContentStore() {
|
||||
AtomicReference<SkillResource> savedResource = new AtomicReference<>();
|
||||
when(skillResourceService.list(any(QueryWrapper.class)))
|
||||
.thenAnswer(invocation -> savedResource.get() == null
|
||||
? List.of() : List.of(savedResource.get()));
|
||||
when(skillResourceService.save(any(SkillResource.class))).thenAnswer(invocation -> {
|
||||
SkillResource resource = invocation.getArgument(0);
|
||||
resource.setId(BigInteger.valueOf(12));
|
||||
savedResource.set(resource);
|
||||
return true;
|
||||
});
|
||||
|
||||
SkillFileContent result = service.uploadResource(
|
||||
SKILL_ID, "scripts/helper.bin", new TestMultipartFile(
|
||||
"helper.bin", new byte[]{0, (byte) 0xFF, 1}));
|
||||
|
||||
assertEquals("SCRIPT", result.getType());
|
||||
assertFalse(result.getIsText());
|
||||
assertEquals(NEW_CONTENT_REF, savedResource.get().getContentRef());
|
||||
assertNull(savedResource.get().getTextContent());
|
||||
verify(contentStore).put(any(MultipartFile.class), anyString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,9 +306,9 @@ public class SkillFileServiceImplTransactionTest {
|
||||
|
||||
SkillFileContent result = service.createTextFile(request);
|
||||
|
||||
assertEquals("SCRIPT", savedResource.get().getKind());
|
||||
assertEquals("SCRIPT", result.getType());
|
||||
assertTrue(savedResource.get().getIsText());
|
||||
assertNull(savedResource.get().getLanguage());
|
||||
assertNull(result.getLanguage());
|
||||
assertEquals("text/plain", savedResource.get().getMediaType());
|
||||
assertEquals("puts 'ok'\n", result.getContent());
|
||||
}
|
||||
@@ -284,8 +321,6 @@ public class SkillFileServiceImplTransactionTest {
|
||||
String sourceHash = "d".repeat(64);
|
||||
SkillResource resource = resource(
|
||||
"references/data.json", true, "{}", null, sourceHash, 2L);
|
||||
resource.setKind("REFERENCE");
|
||||
resource.setLanguage(null);
|
||||
resource.setMediaType("application/json");
|
||||
when(skillResourceService.list(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(resource), List.of(resource));
|
||||
@@ -298,9 +333,9 @@ public class SkillFileServiceImplTransactionTest {
|
||||
|
||||
SkillFileContent result = service.saveContent(request);
|
||||
|
||||
assertEquals("REFERENCE", resource.getKind());
|
||||
assertEquals("REFERENCE", result.getType());
|
||||
assertEquals("application/json", resource.getMediaType());
|
||||
assertNull(resource.getLanguage());
|
||||
assertEquals("JSON", result.getLanguage());
|
||||
assertEquals("application/json", result.getMediaType());
|
||||
assertEquals("{\"ok\":true}\n", result.getContent());
|
||||
}
|
||||
@@ -319,10 +354,8 @@ public class SkillFileServiceImplTransactionTest {
|
||||
persisted.setTenantId(BigInteger.ONE);
|
||||
persisted.setCategoryId(BigInteger.valueOf(3));
|
||||
persisted.setDisplayName("演示 Skill");
|
||||
persisted.setEnabled(false);
|
||||
persisted.setVisibilityScope("DEPT");
|
||||
persisted.setSkillContent(oldContent);
|
||||
persisted.getMetadataJson().put("owner", "qa");
|
||||
AtomicReference<Skill> updateRef = new AtomicReference<>();
|
||||
when(skillService.getOne(any(QueryWrapper.class))).thenReturn(persisted);
|
||||
when(skillService.updateDraftIfContentMatches(any(Skill.class), eq(oldHash)))
|
||||
@@ -344,10 +377,7 @@ public class SkillFileServiceImplTransactionTest {
|
||||
assertEquals(newContent, update.getSkillContent());
|
||||
assertEquals(persisted.getCategoryId(), update.getCategoryId());
|
||||
assertEquals(persisted.getDisplayName(), update.getDisplayName());
|
||||
assertEquals(persisted.getEnabled(), update.getEnabled());
|
||||
assertEquals(persisted.getVisibilityScope(), update.getVisibilityScope());
|
||||
assertNotSame(persisted.getMetadataJson(), update.getMetadataJson());
|
||||
assertEquals(persisted.getMetadataJson(), update.getMetadataJson());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user