test(education): verify category blueprint publication
This commit is contained in:
@@ -31,12 +31,12 @@ ALTER TABLE education_practice_blueprint
|
||||
question_limit IS NULL OR question_limit BETWEEN min_questions AND max_questions),
|
||||
ADD CONSTRAINT ck_education_practice_blueprint_duration CHECK (duration_minutes IS NULL OR duration_minutes > 0);
|
||||
|
||||
CREATE UNIQUE INDEX uk_education_practice_blueprint_tenant_active_node
|
||||
CREATE UNIQUE INDEX uk_education_practice_blueprint_tenant_live_node
|
||||
ON education_practice_blueprint(tenant_id,node_id)
|
||||
WHERE deleted=false AND scope='TENANT_OWNED' AND mode='NODE' AND publication_status='ACTIVE';
|
||||
CREATE UNIQUE INDEX uk_education_practice_blueprint_tenant_active_collection
|
||||
WHERE deleted=false AND scope='TENANT_OWNED' AND mode='NODE' AND publication_status IN ('DRAFT','ACTIVE');
|
||||
CREATE UNIQUE INDEX uk_education_practice_blueprint_tenant_live_collection
|
||||
ON education_practice_blueprint(tenant_id,collection_id)
|
||||
WHERE deleted=false AND scope='TENANT_OWNED' AND mode='COLLECTION' AND publication_status='ACTIVE';
|
||||
WHERE deleted=false AND scope='TENANT_OWNED' AND mode='COLLECTION' AND publication_status IN ('DRAFT','ACTIVE');
|
||||
|
||||
CREATE TABLE education_category_lifecycle_audit (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
@@ -85,7 +85,7 @@ BEGIN
|
||||
IF OLD.scope='TENANT_OWNED' AND NEW.deleted IS DISTINCT FROM OLD.deleted THEN RAISE EXCEPTION 'tenant category cannot be logically deleted' USING ERRCODE='23514'; END IF;
|
||||
IF OLD.publication_status='ARCHIVED' AND NEW IS DISTINCT FROM OLD THEN RAISE EXCEPTION 'archived category is immutable' USING ERRCODE='23514'; END IF;
|
||||
IF NEW.authoring_version<>OLD.authoring_version+1 THEN RAISE EXCEPTION 'category version must advance once' USING ERRCODE='23514'; END IF;
|
||||
IF OLD.publication_status<>'DRAFT' AND (NEW.subject_id IS DISTINCT FROM OLD.subject_id OR NEW.name IS DISTINCT FROM OLD.name OR NEW.sort_order IS DISTINCT FROM OLD.sort_order) THEN RAISE EXCEPTION 'active category is immutable' USING ERRCODE='23514'; END IF;
|
||||
IF OLD.publication_status<>'DRAFT' AND (NEW.subject_id IS DISTINCT FROM OLD.subject_id OR NEW.legacy_node_id IS DISTINCT FROM OLD.legacy_node_id OR NEW.name IS DISTINCT FROM OLD.name OR NEW.sort_order IS DISTINCT FROM OLD.sort_order) THEN RAISE EXCEPTION 'active category is immutable' USING ERRCODE='23514'; END IF;
|
||||
IF NEW.publication_status IS DISTINCT FROM OLD.publication_status THEN
|
||||
IF NOT ((OLD.publication_status='DRAFT' AND NEW.publication_status='ACTIVE') OR (OLD.publication_status='ACTIVE' AND NEW.publication_status='ARCHIVED')) THEN RAISE EXCEPTION 'invalid category lifecycle' USING ERRCODE='23514'; END IF;
|
||||
EXECUTE format('INSERT INTO %I.education_category_lifecycle_transition_token VALUES($1,$2,$3,$4,$5,$6)',TG_TABLE_SCHEMA) USING pg_current_xact_id()::text::BIGINT,NEW.tenant_id,NEW.id,NEW.authoring_version,OLD.publication_status,NEW.publication_status;
|
||||
|
||||
@@ -68,9 +68,10 @@ class PracticeBlueprintAuthoringPostgreSqlIntegrationTest extends PostgreSqlDbIn
|
||||
|
||||
@Test void activeBlueprintContentAndAuditAreDatabaseImmutable() throws Exception {
|
||||
Long id = service.createDraft(command(null)); service.activate(id, 0, 7L);
|
||||
assertThrows(Exception.class, () -> blueprintMapper.updateDraftCas(10L, id,
|
||||
blueprintMapper.selectTenantOwnedById(10L, id), 1));
|
||||
PracticeBlueprintLifecycleAuditDO audit = auditMapper.selectOne();
|
||||
PracticeBlueprintDO active = blueprintMapper.selectTenantOwnedById(10L, id);
|
||||
active.setSuggestedCount(1);
|
||||
assertEquals(0, blueprintMapper.updateDraftCas(10L, id, active, 1));
|
||||
PracticeBlueprintLifecycleAuditDO audit = auditMapper.selectOne(PracticeBlueprintLifecycleAuditDO::getBlueprintId, id);
|
||||
audit.setActorId(8L); assertThrows(Exception.class, () -> auditMapper.updateById(audit));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.module.education.service.category.authoring;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.module.education.config.EducationProperties;
|
||||
import cn.iocoder.yudao.module.education.dal.dataobject.catalog.*;
|
||||
import cn.iocoder.yudao.module.education.dal.mysql.catalog.*;
|
||||
import cn.iocoder.yudao.module.education.enums.CatalogProviderMode;
|
||||
import cn.iocoder.yudao.module.education.test.PostgreSqlDbIntegrationTest;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import static cn.iocoder.yudao.module.education.enums.ErrorCodeConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Import({CategoryAuthoringServiceImpl.class, EducationProperties.class,
|
||||
cn.iocoder.yudao.module.education.service.catalog.provider.JavaCatalogProvider.class})
|
||||
@TestPropertySource(properties = {"yudao.education.enabled=true", "yudao.education.catalog-mode=JAVA_READ"})
|
||||
class CategoryAuthoringPostgreSqlIntegrationTest extends PostgreSqlDbIntegrationTest {
|
||||
@Resource EducationProperties properties;
|
||||
@Resource CategoryAuthoringService service;
|
||||
@Resource CategoryMapper categoryMapper;
|
||||
@Resource CategoryLifecycleAuditMapper auditMapper;
|
||||
@Resource SubjectMapper subjectMapper;
|
||||
@Resource cn.iocoder.yudao.module.education.service.catalog.provider.JavaCatalogProvider catalogProvider;
|
||||
|
||||
@BeforeEach void setUp() {
|
||||
properties.setCatalogMode(CatalogProviderMode.JAVA_READ); TenantContextHolder.setTenantId(10L);
|
||||
SubjectDO subject = new SubjectDO(); subject.setId(100L); subject.setTenantId(10L); subject.setScope("TENANT_OWNED");
|
||||
subject.setName("Subject"); subject.setIsActive(true); subjectMapper.insert(subject);
|
||||
}
|
||||
@AfterEach void clear() { TenantContextHolder.clear(); }
|
||||
|
||||
@Test void draftActiveArchiveControlsStudentVisibilityAndAudit() {
|
||||
Long id = service.createDraft(command(null));
|
||||
assertTrue(catalogProvider.listCategories("100", null).isEmpty());
|
||||
assertEquals(1, service.activate(id, 0, 7L));
|
||||
assertEquals(1, catalogProvider.listCategories("100", null).size());
|
||||
assertEquals(2, service.archive(id, 1, 7L));
|
||||
assertTrue(catalogProvider.listCategories("100", null).isEmpty());
|
||||
assertEquals(2L, auditMapper.selectCount());
|
||||
}
|
||||
|
||||
@Test void unsupportedCrossTenantAndStaleFailClosed() {
|
||||
properties.setCatalogMode(CatalogProviderMode.SCALAR_READ);
|
||||
ServiceException unsupported = assertThrows(ServiceException.class, () -> service.createDraft(command(null)));
|
||||
assertEquals(CATEGORY_AUTHORING_PROVIDER_UNSUPPORTED.getCode(), unsupported.getCode());
|
||||
properties.setCatalogMode(CatalogProviderMode.JAVA_READ); Long id = service.createDraft(command(null));
|
||||
TenantContextHolder.setTenantId(20L);
|
||||
ServiceException notFound = assertThrows(ServiceException.class, () -> service.activate(id, 0, 8L));
|
||||
assertEquals(CATEGORY_AUTHORING_NOT_FOUND.getCode(), notFound.getCode());
|
||||
TenantContextHolder.setTenantId(10L); service.activate(id, 0, 7L);
|
||||
ServiceException stale = assertThrows(ServiceException.class, () -> service.archive(id, 0, 7L));
|
||||
assertEquals(CATEGORY_AUTHORING_CONFLICT.getCode(), stale.getCode());
|
||||
}
|
||||
|
||||
@Test void lifecycleAuditIsAppendOnly() {
|
||||
Long id = service.createDraft(command(null)); service.activate(id, 0, 7L);
|
||||
CategoryLifecycleAuditDO audit = auditMapper.selectOne(CategoryLifecycleAuditDO::getCategoryId, id);
|
||||
audit.setActorId(8L); assertThrows(Exception.class, () -> auditMapper.updateById(audit));
|
||||
}
|
||||
|
||||
private CategoryAuthoringCommand command(Integer version) {
|
||||
return new CategoryAuthoringCommand(100L, null, "Category", 0, version);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ TRUNCATE TABLE
|
||||
education_entitlement_event,
|
||||
education_entitlement,
|
||||
education_resource_product_binding,
|
||||
education_practice_blueprint_lifecycle_audit,
|
||||
education_category_lifecycle_audit,
|
||||
education_practice_blueprint,
|
||||
education_category,
|
||||
education_question_collection_lifecycle_audit,
|
||||
education_content_node_lifecycle_audit,
|
||||
education_question_lifecycle_transition_token,
|
||||
@@ -25,5 +29,6 @@ TRUNCATE TABLE
|
||||
education_practice_question,
|
||||
education_practice_session,
|
||||
education_content_node,
|
||||
education_content_entry
|
||||
education_content_entry,
|
||||
education_subject
|
||||
RESTART IDENTITY CASCADE;
|
||||
|
||||
Reference in New Issue
Block a user