test(education): verify category blueprint publication
This commit is contained in:
@@ -126,6 +126,10 @@ The durable artifact classification, adoption matrix, version allocation, backfi
|
||||
17. Activation exposes the collection route only when the collection and its existing Content Node are available. Archiving closes only collection-route discovery; it does not alter direct Question visibility or historical Practice Question Snapshots.
|
||||
18. `access_rules` is descriptive reserved metadata in this slice, not entitlement enforcement. PUBLIC Question curation, dynamic filters, paid/private access, Category, Practice Blueprint, and Content Entry creation are excluded.
|
||||
|
||||
19. V4120 closes the bounded EDU-010 Category and Practice Blueprint scope. Category is a standalone Subject-scoped catalog aggregate, not Question classification. Blueprint authoring is limited to exactly one current-tenant NODE or active Manual COLLECTION target; target ownership, route, and counts are server-derived.
|
||||
20. Category and Practice Blueprint use the same `DRAFT → ACTIVE → ARCHIVED`, `authoring_version` CAS, transactional append-only audit, current-tenant ownership, and `JAVA_READ` authority rules as Content Node and Collection. Student reads require ACTIVE lifecycle plus route availability.
|
||||
21. The six added permissions are `education:category:{author,publish,archive}` and `education:practice-blueprint:{author,publish,archive}`. V4120 conditionally seeds permission rows at IDs 6809-6814, fails on conflicting IDs, and assigns no roles.
|
||||
|
||||
**Permission and data-scope matrix:**
|
||||
|
||||
| Actor capability | Permission | Allowed rows | Data scope |
|
||||
@@ -140,6 +144,12 @@ The durable artifact classification, adoption matrix, version allocation, backfi
|
||||
| Collection author | `education:collection:author` | Create/revise current-tenant TENANT_OWNED Manual Question Collection drafts and replace ordered membership | Current tenant; DRAFT-only; existing ACTIVE node and PUBLISHED tenant questions |
|
||||
| Collection publisher | `education:collection:publish` | Activate current-tenant TENANT_OWNED Manual Question Collection drafts | Tenant-wide shared catalog asset; authoring-version CAS |
|
||||
| Collection archiver | `education:collection:archive` | Archive current-tenant TENANT_OWNED active Manual Question Collections | Tenant-wide shared catalog asset; authoring-version CAS |
|
||||
| Category author | `education:category:author` | Create/revise current-tenant TENANT_OWNED Category drafts | Current tenant; active PUBLIC or same-tenant Subject reference |
|
||||
| Category publisher | `education:category:publish` | Activate current-tenant TENANT_OWNED Category drafts | Tenant-wide shared catalog asset; authoring-version CAS |
|
||||
| Category archiver | `education:category:archive` | Archive current-tenant TENANT_OWNED active Categories | Tenant-wide shared catalog asset; authoring-version CAS |
|
||||
| Practice Blueprint author | `education:practice-blueprint:author` | Create/revise bounded NODE or COLLECTION blueprint drafts | Current tenant; target ownership and counts derived server-side |
|
||||
| Practice Blueprint publisher | `education:practice-blueprint:publish` | Activate current-tenant TENANT_OWNED blueprint drafts | Tenant-wide shared catalog asset; target revalidation and CAS |
|
||||
| Practice Blueprint archiver | `education:practice-blueprint:archive` | Archive current-tenant TENANT_OWNED active blueprints | Tenant-wide shared catalog asset; authoring-version CAS |
|
||||
| Platform curator | none in this slice | No PUBLIC writes through these endpoints | Fail closed |
|
||||
|
||||
V4080/V4090 conditionally seed the five Education permissions (`education:capability` plus author/classify/publish/archive). V4110 conditionally seeds the collection author/publish/archive permissions at IDs 6806-6808. Seeds run only when `system_menu` exists, fail when a fixed ID is occupied by a different permission, and deliberately assign no role.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# EDU-010 — Tenant content publication and graph integrity
|
||||
|
||||
- **Status:** in progress — catalog graph-integrity, JAVA_READ tenant-question lifecycle, Question Placement, tenant Content Node lifecycle, and Manual Question Collection slices are delivered; category and blueprint authoring remain
|
||||
- **Status:** done — bounded JAVA_READ tenant Question, Placement, Content Node, Manual Collection, Category, and Practice Blueprint authoring/publication lifecycles are delivered; broader excluded workflows remain separately scoped
|
||||
- **Type:** implementation program
|
||||
- **Phase:** 3
|
||||
- **Blockers:** EDU-004 ✓ (done), EDU-009 ✓ (done), provider-authority decision ✓ (resolved 2026-07-30, see decisions.md), PUBLIC graph-semantics decision ✓ (resolved 2026-07-30, see decisions.md)
|
||||
@@ -132,7 +132,23 @@ Accepted boundary:
|
||||
|
||||
No new ADR is added: this bounded slice consistently applies the existing native-authority and lifecycle decisions and does not introduce a separate hard-to-reverse architectural trade-off.
|
||||
|
||||
## Verification evidence (2026-07-30)
|
||||
## Delivery progress — JAVA_READ Category and Practice Blueprint slice (2026-07-31)
|
||||
|
||||
Delivered:
|
||||
|
||||
- Category draft create/revise, activate, and archive commands manage only current-tenant `TENANT_OWNED` rows. Subject ownership is server-validated against active PUBLIC or same-tenant Subjects; student category discovery requires `publication_status='ACTIVE'` and `is_active=true`.
|
||||
- Practice Blueprint draft create/revise, activate, and archive commands support only bounded `NODE` and `COLLECTION` modes. Exactly one target is selected by the request, while `entry_id`, effective node, tenant/scope, eligible/total counts, and lifecycle are server-controlled.
|
||||
- NODE blueprints require an existing ACTIVE visible current-tenant Content Node and count current-tenant Published Questions on that node. COLLECTION blueprints require an ACTIVE current-tenant Manual Question Collection and derive counts from its maintained membership count.
|
||||
- Both aggregates use `DRAFT → ACTIVE → ARCHIVED`, one monotonic `authoring_version` CAS, immutable ACTIVE content, terminal ARCHIVED state, transactional actor/version/status audit, append-only audit tables, and fail-before-mapper `JAVA_READ` authority checks.
|
||||
- Separate Category and Practice Blueprint author/publish/archive permissions are conditionally seeded by V4120 without assigning any role. PUBLIC/platform-curator writes remain unavailable.
|
||||
- Student blueprint lookup uses one availability query across the blueprint, Content Node, Content Entry, and optional Collection so draft, archived, or route-unavailable blueprints fail closed.
|
||||
- `V4120__add_category_and_practice_blueprint_authoring.sql` is the only new migration version and was executed by focused real-PostgreSQL tests.
|
||||
|
||||
## Remaining exclusions after EDU-010
|
||||
|
||||
EDU-010 intentionally does not deliver PUBLIC/platform-curator authoring, Content Entry authoring, Question Bank authoring, dynamic/filter blueprints, mixed or descendant-node blueprint selection, type/difficulty-specific authoring semantics, paid/private entitlement enforcement, Retire/restore transitions, per-member score/required flags, legacy asset/import workflows, or administrative list/detail/delete endpoints. Category remains a separately discoverable catalog aggregate and is not a Question relationship; Question classification continues through Placement.
|
||||
|
||||
## Verification evidence (2026-07-31)
|
||||
|
||||
- `mvn -pl yudao-module-education clean test` with the five `EDU_TEST_POSTGRES_*` variables pointed at the local Docker PostgreSQL: **502 tests passed**, including 20 Flyway migration tests and nine lifecycle/placement PostgreSQL integration tests. V4090 was actually executed in disposable PostgreSQL schemas.
|
||||
- The lifecycle integration tests prove concurrent double-publish has one success and one lifecycle conflict with exactly one publish audit, cross-tenant and PUBLIC management fail closed, and `draft → publish → archive` matches student visibility.
|
||||
|
||||
@@ -36,7 +36,7 @@ EDU-000 Phase 0 artifacts done
|
||||
└── EDU-005 PostgreSQL/Flyway takeover decision done
|
||||
|
||||
EDU-009 + provider/content decisions
|
||||
└── EDU-010 Tenant content publication in progress (Manual Collection delivered; category/blueprint remain)
|
||||
└── EDU-010 Tenant content publication done (bounded JAVA_READ publication scope delivered)
|
||||
└── EDU-011 Import/export/assets/scanning blocked; contract disposition exposed by capability manifest
|
||||
|
||||
EDU-004
|
||||
|
||||
@@ -104,21 +104,17 @@ _Avoid_: Question publication, snapshot invalidation
|
||||
Descriptive collection metadata reserved for a future access contract. Its presence does not grant, deny, or prove paid, private, membership, or other entitlement in the current Education model.
|
||||
_Avoid_: Entitlement policy, authorization rule
|
||||
|
||||
**Vocabulary Review State**:
|
||||
Education-owned, tenant-and-student scoped progress for one explicit vocabulary key. Review outcomes are bounded to Again, Hard, Good, or Easy and deterministically move mastery and the next review time; this is learning state, not catalog authority.
|
||||
_Avoid_: Shared dictionary ownership, client-selected mastery
|
||||
**Category Publication State**:
|
||||
The lifecycle of a tenant-owned Subject-scoped Category: Draft is author-editable and student-invisible, Active is immutable and discoverable, and Archived is terminal and undiscoverable. Category does not classify Questions.
|
||||
_Avoid_: Question Placement, question category relation
|
||||
|
||||
**Exam Reminder Plan**:
|
||||
A student-owned schedule that Education durably claims and dispatches through System's `NotifyMessageSendApi`. The reminder row is the retry and compatibility authority; the System notify message is the delivery result, not the schedule itself.
|
||||
_Avoid_: Direct System table write, best-effort timer
|
||||
**Practice Blueprint**:
|
||||
A bounded tenant-owned practice configuration attached to exactly one active current-tenant Content Node or Manual Question Collection. Its route and eligible counts are server-derived; it does not define dynamic filtering, entitlement, or descendant traversal.
|
||||
_Avoid_: Dynamic query, paid practice policy
|
||||
|
||||
**Learning Award Orchestration**:
|
||||
Education's idempotent record of one bounded learning event and its point/badge outcome. Point balance remains Member-owned and is changed only through `MemberPointApi`; clients select a supported event family, never a point amount or badge code.
|
||||
_Avoid_: Education point ledger, arbitrary client reward
|
||||
|
||||
**Tenant Learning Leaderboard**:
|
||||
A bounded current-tenant projection of awarded Education learning points. It exposes deterministic anonymous aliases and aggregate scores only; member identifiers and detailed reports are excluded.
|
||||
_Avoid_: Cross-tenant ranking, student profile export
|
||||
**Practice Blueprint Publication State**:
|
||||
The lifecycle of a Practice Blueprint: Draft is author-editable and student-invisible, Active is immutable and returned only while its target route remains available, and Archived is terminal and unavailable.
|
||||
_Avoid_: Boolean availability as an independent state
|
||||
|
||||
**Protected Answer Key**:
|
||||
Server-only correctness and explanation data captured for stable scoring of a practice session. It is never included in a Safe Question, Question Snapshot JSON, or pre-submit response.
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
- 答案保存端点 (见下方 Answer API) — 幂等保存,安全重试
|
||||
- 题目安全过滤(答案/解析绝不暴露到前端)
|
||||
- 管理端题目创作、目录放置、发布和归档端点 — 仅在 `JAVA_READ` 下接受写入
|
||||
- 租户班级、现有 Member 学生/教师关系,以及具备过期与审计语义的幂等邀请创建/接受
|
||||
- 独立的功能开关配置 + Scalar 数据源配置
|
||||
- 错误码常量(通用 + 租户 + Catalog/Scalar + 题目/练习)
|
||||
- System RBAC 权限注解及 V4080/V4090 条件种子(`education:capability`、`education:question:author`、`education:question:classify`、`education:question:publish`、`education:question:archive`);迁移不自动向任何角色授权
|
||||
@@ -126,20 +125,6 @@ GET /admin-api/education/capability
|
||||
| `1_005_003_075` | 归类版本或题目状态已变化 |
|
||||
| `1_005_003_076` | 题目尚未归类,不能发布 |
|
||||
|
||||
### 管理后台与用户 APP - 班级关系
|
||||
|
||||
班级只属于当前框架租户,关系引用当前租户已经存在的 Member 用户。Education 不创建账号、不保存密码,也不把 `STUDENT`/`TEACHER` 班级角色映射为 System RBAC 角色。
|
||||
|
||||
| 端点 | 权限 | 说明 |
|
||||
|------|------|------|
|
||||
| `POST /admin-api/education/classes` | `education:class:create` | 创建当前租户班级 |
|
||||
| `GET /admin-api/education/classes` | `education:class:query` | 查询当前租户班级 |
|
||||
| `GET /admin-api/education/classes/{classId}/members` | `education:class-member:query` | 查询指定班级关系 |
|
||||
| `POST /admin-api/education/classes/{classId}/invitations` | `education:class-invitation:create` | 幂等邀请现有 Member 用户 |
|
||||
| `POST /app-api/education/class-invitations/{invitationId}/accept` | Member 登录态 | 邀请本人幂等接受 |
|
||||
|
||||
邀请创建的幂等范围是当前租户、创建人和 `idempotencyKey`;同键不同请求返回 `1_005_004_006`。接受时锁定邀请,校验邀请人、过期时间和班级状态,在同一事务最多创建一条关系和一条 `ACCEPTED` 审计。跨租户班级、关系与邀请在 Mapper 和 PostgreSQL trigger 两层失败关闭。V4140 是该能力唯一 migration。
|
||||
|
||||
### 用户 APP - 教育租户识别
|
||||
|
||||
```
|
||||
@@ -254,8 +239,7 @@ yudao-module-education/src/main/resources/db/migration/education/
|
||||
- `V4080` 增加第 20 条目录引用边 `education_question_version.question_id → education_question.id`,并为版本表增加第 12 个 ownership/scope 守卫;版本归属必须与题目完全一致。该迁移还建立 draft-first 生命周期、不可变题目版本、同事务追加式审计及历史已发布内容的 fail-closed 预检。
|
||||
- `V4090` 增加 Question Placement 乐观版本、PUBLIC 管理拒绝、发布后放置冻结、发布前可用节点约束,以及 `education:question:classify` 权限种子。
|
||||
- `V4100` 增加租户 Content Node 的 `DRAFT → ACTIVE → ARCHIVED` 生命周期、统一 `authoring_version` CAS、同事务追加式审计和 entry/parent 结构约束;不写入权限或角色种子。
|
||||
- `V4110` 增加租户手工题集的生命周期、完整有序成员替换和学生发现路径约束。
|
||||
- `V4160` 仅用于 EDU-014 有界学习波次:新增词汇复习状态、考试提醒计划、学习奖励编排和学生反馈表,并在 System 通知模板表存在时条件安装 `education_exam_reminder` 模板。视频与 AI 不获得任何访问或权益能力。
|
||||
- `V4120` 增加租户 Category 与 bounded Practice Blueprint 的 `DRAFT → ACTIVE → ARCHIVED` 生命周期、统一 `authoring_version` CAS、事务内追加式审计、独立 RBAC 和学生端路由可见性约束;不授予任何角色。
|
||||
- V4080/V4090 在平台 `system_menu` 已存在时条件写入 Education 权限种子;固定 ID 已被不同 permission 占用时迁移失败,且迁移不会向角色写入授权关系。
|
||||
- 旧 `education_answer_idempotency`、`education_submit_idempotency` 在首次接管时保留,后续清理必须使用更高版本的独立向前 migration。
|
||||
- `sql/postgresql/education/` 是手工初始化/设计历史,`sql/mysql/education/` 是过时归档;两者都不是运行时交付入口。
|
||||
|
||||
@@ -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