mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): reject reserved page types (#601)
This commit is contained in:
parent
770198ba69
commit
9ba188b4cb
@ -33,6 +33,13 @@ public class WikiPageTypeProfileService {
|
|||||||
private final WikiPageTypeProfileMapper profileMapper;
|
private final WikiPageTypeProfileMapper profileMapper;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved page types are internal control-plane types. User profiles must
|
||||||
|
* never route business pages into them, because downstream list/search and
|
||||||
|
* cleanup paths intentionally treat them specially.
|
||||||
|
*/
|
||||||
|
private static final Set<String> RESERVED_PAGE_TYPES = Set.of("system", "synthesis");
|
||||||
|
|
||||||
/** Parsed once at startup; immutable thereafter. */
|
/** Parsed once at startup; immutable thereafter. */
|
||||||
private WikiPageTypeProfile defaultProfile;
|
private WikiPageTypeProfile defaultProfile;
|
||||||
|
|
||||||
@ -209,10 +216,9 @@ public class WikiPageTypeProfileService {
|
|||||||
* @throws IllegalArgumentException when {@code configJson} does not parse
|
* @throws IllegalArgumentException when {@code configJson} does not parse
|
||||||
*/
|
*/
|
||||||
public void saveProfile(Long kbId, String name, String configJson) {
|
public void saveProfile(Long kbId, String name, String configJson) {
|
||||||
try {
|
java.util.List<String> issues = validateProfileJson(configJson);
|
||||||
objectMapper.readValue(configJson, WikiPageTypeProfile.class);
|
if (!issues.isEmpty()) {
|
||||||
} catch (Exception e) {
|
throw new IllegalArgumentException(String.join("; ", issues));
|
||||||
throw new IllegalArgumentException("Invalid profile config JSON: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
WikiPageTypeProfileEntity existing = findEnabledRow(kbId);
|
WikiPageTypeProfileEntity existing = findEnabledRow(kbId);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
@ -262,6 +268,16 @@ public class WikiPageTypeProfileService {
|
|||||||
issues.add("Profile declares no pageTypes");
|
issues.add("Profile declares no pageTypes");
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
profile.getPageTypes().keySet().forEach(typeName -> {
|
||||||
|
String normalized = normalizeTypeName(typeName);
|
||||||
|
if (RESERVED_PAGE_TYPES.contains(normalized)) {
|
||||||
|
issues.add(typeName + ": reserved pageType is not allowed in user profiles");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
String fallback = normalizeTypeName(profile.getFallbackType());
|
||||||
|
if (RESERVED_PAGE_TYPES.contains(fallback)) {
|
||||||
|
issues.add("reserved fallbackType is not allowed: " + fallback);
|
||||||
|
}
|
||||||
java.util.Set<String> validTypes = java.util.Set.of(
|
java.util.Set<String> validTypes = java.util.Set.of(
|
||||||
"string", "number", "boolean", "date", "enum", "string_array");
|
"string", "number", "boolean", "date", "enum", "string_array");
|
||||||
profile.getPageTypes().forEach((typeName, def) -> {
|
profile.getPageTypes().forEach((typeName, def) -> {
|
||||||
@ -283,15 +299,40 @@ public class WikiPageTypeProfileService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalise a routed/created pageType against the KB profile: a declared
|
* Normalise a routed/created pageType against the KB profile: a declared
|
||||||
* type is returned as-is (lowercase); an unknown type is downgraded to the
|
* type is returned as-is (lowercase); an unknown or reserved type is
|
||||||
* profile's {@code fallbackType}. Never returns null.
|
* downgraded to a safe fallback. Never returns null.
|
||||||
*/
|
*/
|
||||||
public String normalizePageType(Long kbId, String pageType) {
|
public String normalizePageType(Long kbId, String pageType) {
|
||||||
WikiPageTypeProfile profile = resolveProfile(kbId);
|
WikiPageTypeProfile profile = resolveProfile(kbId);
|
||||||
if (pageType != null && profile.hasPageType(pageType)) {
|
String normalized = normalizeTypeName(pageType);
|
||||||
return pageType.trim().toLowerCase();
|
if (normalized != null && !RESERVED_PAGE_TYPES.contains(normalized)
|
||||||
|
&& profile.hasPageType(normalized)) {
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
String fallback = profile.getFallbackType();
|
return safeFallbackType(profile);
|
||||||
return fallback == null ? "concept" : fallback.trim().toLowerCase();
|
}
|
||||||
|
|
||||||
|
private static String safeFallbackType(WikiPageTypeProfile profile) {
|
||||||
|
String fallback = profile == null ? null : normalizeTypeName(profile.getFallbackType());
|
||||||
|
if (profile != null && fallback != null && !RESERVED_PAGE_TYPES.contains(fallback)
|
||||||
|
&& profile.hasPageType(fallback)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
if (profile != null && profile.hasPageType("concept")) {
|
||||||
|
return "concept";
|
||||||
|
}
|
||||||
|
return profile == null ? "concept" : profile.getPageTypes().keySet().stream()
|
||||||
|
.map(WikiPageTypeProfileService::normalizeTypeName)
|
||||||
|
.filter(type -> type != null && !RESERVED_PAGE_TYPES.contains(type))
|
||||||
|
.findFirst()
|
||||||
|
.orElse("concept");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeTypeName(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = type.trim().toLowerCase();
|
||||||
|
return normalized.isEmpty() ? null : normalized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,17 @@ class WikiPageTypeProfileServiceE2ETest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reservedPageType_isRejectedOnSave() {
|
||||||
|
try {
|
||||||
|
service.saveProfile(SEQ.incrementAndGet(), "bad",
|
||||||
|
"{\"pageTypes\":{\"system\":{\"label\":\"System\"},\"concept\":{\"label\":\"Concept\"}}}");
|
||||||
|
org.junit.jupiter.api.Assertions.fail("expected IllegalArgumentException");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertTrue(expected.getMessage().contains("reserved pageType"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validateProfileJson_reportsIssues() {
|
void validateProfileJson_reportsIssues() {
|
||||||
assertTrue(service.validateProfileJson(EPISODE_JSON).isEmpty());
|
assertTrue(service.validateProfileJson(EPISODE_JSON).isEmpty());
|
||||||
@ -103,4 +114,15 @@ class WikiPageTypeProfileServiceE2ETest {
|
|||||||
"{\"pageTypes\":{\"x\":{\"schema\":{\"f\":{\"type\":\"banana\"}}}}}");
|
"{\"pageTypes\":{\"x\":{\"schema\":{\"f\":{\"type\":\"banana\"}}}}}");
|
||||||
assertTrue(badType.stream().anyMatch(s -> s.contains("unknown field type")));
|
assertTrue(badType.stream().anyMatch(s -> s.contains("unknown field type")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateProfileJson_rejectsReservedPageTypesAndFallback() {
|
||||||
|
List<String> reservedType = service.validateProfileJson(
|
||||||
|
"{\"pageTypes\":{\"system\":{\"label\":\"System\"},\"concept\":{\"label\":\"Concept\"}}}");
|
||||||
|
assertTrue(reservedType.stream().anyMatch(s -> s.contains("reserved pageType")));
|
||||||
|
|
||||||
|
List<String> reservedFallback = service.validateProfileJson(
|
||||||
|
"{\"fallbackType\":\"synthesis\",\"pageTypes\":{\"concept\":{\"label\":\"Concept\"}}}");
|
||||||
|
assertTrue(reservedFallback.stream().anyMatch(s -> s.contains("reserved fallbackType")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,6 +81,22 @@ class WikiPageTypeProfileServiceTest {
|
|||||||
assertEquals("concept", service.normalizePageType(1L, null));
|
assertEquals("concept", service.normalizePageType(1L, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void normalizePageType_rejectsReservedProfileTypesEvenWhenPersisted() {
|
||||||
|
WikiPageTypeProfileEntity row = new WikiPageTypeProfileEntity();
|
||||||
|
row.setKbId(1L);
|
||||||
|
row.setEnabled(1);
|
||||||
|
row.setConfigJson("{\"fallbackType\":\"synthesis\",\"pageTypes\":{"
|
||||||
|
+ "\"system\":{\"label\":\"System\"},"
|
||||||
|
+ "\"synthesis\":{\"label\":\"Synthesis\"},"
|
||||||
|
+ "\"concept\":{\"label\":\"Concept\"}}}");
|
||||||
|
when(mapper.selectOne(any())).thenReturn(row);
|
||||||
|
|
||||||
|
assertEquals("concept", service.normalizePageType(1L, "system"));
|
||||||
|
assertEquals("concept", service.normalizePageType(1L, "synthesis"));
|
||||||
|
assertEquals("concept", service.normalizePageType(1L, "unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void describeForPrompt_defaultProfile_listsBuiltInTypes() {
|
void describeForPrompt_defaultProfile_listsBuiltInTypes() {
|
||||||
when(mapper.selectOne(any())).thenReturn(null);
|
when(mapper.selectOne(any())).thenReturn(null);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user