feat(education): add content import persistence
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.education.dal.dataobject.importjob;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@TableName("education_content_import_asset")
|
||||
@KeySequence("education_content_import_asset_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ContentImportAssetDO extends TenantBaseDO {
|
||||
@TableId
|
||||
private Long id;
|
||||
private String objectKey;
|
||||
private String fileName;
|
||||
private String mimeType;
|
||||
private Long fileSizeBytes;
|
||||
private String checksumSha256;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.education.dal.dataobject.importjob;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@TableName(value = "education_content_import_job", autoResultMap = true)
|
||||
@KeySequence("education_content_import_job_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ContentImportJobDO extends TenantBaseDO {
|
||||
@TableId
|
||||
private Long id;
|
||||
private Long assetId;
|
||||
private String previewKey;
|
||||
private String importType;
|
||||
private String status;
|
||||
private Integer attemptCount;
|
||||
private Integer maxAttempts;
|
||||
private LocalDateTime nextAttemptAt;
|
||||
private String lockedBy;
|
||||
private String leaseToken;
|
||||
private LocalDateTime leaseExpiresAt;
|
||||
private LocalDateTime lastHeartbeatAt;
|
||||
private LocalDateTime startedAt;
|
||||
private LocalDateTime finishedAt;
|
||||
private String errorMessage;
|
||||
@TableField("preview_payload")
|
||||
private String previewPayload;
|
||||
@TableField("result_summary")
|
||||
private String resultSummary;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.iocoder.yudao.module.education.dal.mysql.importjob;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.education.dal.dataobject.importjob.ContentImportAssetDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ContentImportAssetMapper extends BaseMapperX<ContentImportAssetDO> {
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package cn.iocoder.yudao.module.education.dal.mysql.importjob;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.education.dal.dataobject.importjob.ContentImportJobDO;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ContentImportJobMapper extends BaseMapperX<ContentImportJobDO> {
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO education_content_import_job
|
||||
(tenant_id, asset_id, preview_key, import_type, status, attempt_count, max_attempts,
|
||||
preview_payload, creator, create_time, updater, update_time, deleted)
|
||||
VALUES
|
||||
(#{tenantId}, #{assetId}, #{previewKey}, #{importType}, #{status}, 0, #{maxAttempts},
|
||||
CAST(#{previewPayload} AS JSONB), '', CURRENT_TIMESTAMP, '', CURRENT_TIMESTAMP, FALSE)
|
||||
ON CONFLICT (tenant_id, preview_key) DO NOTHING
|
||||
""")
|
||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||
int insertPreviewIgnore(ContentImportJobDO job);
|
||||
|
||||
@Update("""
|
||||
UPDATE education_content_import_job
|
||||
SET status = 'PENDING', next_attempt_at = CURRENT_TIMESTAMP, update_time = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId} AND id = #{id} AND status = 'PREVIEW' AND deleted = FALSE
|
||||
""")
|
||||
int executePreview(@Param("tenantId") Long tenantId, @Param("id") Long id);
|
||||
|
||||
@Select("""
|
||||
WITH candidates AS (
|
||||
SELECT id FROM education_content_import_job
|
||||
WHERE deleted = FALSE AND attempt_count < max_attempts
|
||||
AND ((status = 'PENDING' AND (next_attempt_at IS NULL OR next_attempt_at <= CURRENT_TIMESTAMP))
|
||||
OR (status = 'PROCESSING' AND lease_expires_at <= CURRENT_TIMESTAMP))
|
||||
ORDER BY CASE WHEN status = 'PROCESSING' THEN 0 ELSE 1 END,
|
||||
COALESCE(lease_expires_at, next_attempt_at, create_time), id
|
||||
LIMIT #{limit} FOR UPDATE SKIP LOCKED
|
||||
)
|
||||
UPDATE education_content_import_job job
|
||||
SET status = 'PROCESSING', locked_by = #{workerId}, lease_token = #{leaseToken},
|
||||
lease_expires_at = CURRENT_TIMESTAMP + CAST(#{leaseSeconds} || ' seconds' AS INTERVAL),
|
||||
last_heartbeat_at = CURRENT_TIMESTAMP, attempt_count = job.attempt_count + 1,
|
||||
next_attempt_at = NULL, started_at = COALESCE(job.started_at, CURRENT_TIMESTAMP),
|
||||
finished_at = NULL, error_message = NULL, update_time = CURRENT_TIMESTAMP
|
||||
FROM candidates WHERE job.id = candidates.id
|
||||
RETURNING job.*
|
||||
""")
|
||||
List<ContentImportJobDO> claimReady(@Param("workerId") String workerId,
|
||||
@Param("leaseToken") String leaseToken,
|
||||
@Param("leaseSeconds") long leaseSeconds,
|
||||
@Param("limit") int limit);
|
||||
|
||||
@Update("""
|
||||
UPDATE education_content_import_job
|
||||
SET lease_expires_at = CURRENT_TIMESTAMP + CAST(#{leaseSeconds} || ' seconds' AS INTERVAL),
|
||||
last_heartbeat_at = CURRENT_TIMESTAMP, update_time = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId} AND id = #{id} AND status = 'PROCESSING'
|
||||
AND lease_token = #{leaseToken} AND lease_expires_at > CURRENT_TIMESTAMP
|
||||
""")
|
||||
int heartbeat(@Param("tenantId") Long tenantId, @Param("id") Long id,
|
||||
@Param("leaseToken") String leaseToken, @Param("leaseSeconds") long leaseSeconds);
|
||||
|
||||
@Update("""
|
||||
WITH expired AS (
|
||||
SELECT id FROM education_content_import_job
|
||||
WHERE status = 'PROCESSING' AND lease_expires_at <= CURRENT_TIMESTAMP
|
||||
AND attempt_count < max_attempts
|
||||
ORDER BY lease_expires_at, id
|
||||
LIMIT #{limit} FOR UPDATE SKIP LOCKED
|
||||
)
|
||||
UPDATE education_content_import_job job
|
||||
SET status = 'PENDING', locked_by = NULL, lease_token = NULL, lease_expires_at = NULL,
|
||||
last_heartbeat_at = NULL, next_attempt_at = CURRENT_TIMESTAMP, update_time = CURRENT_TIMESTAMP
|
||||
FROM expired WHERE job.id = expired.id
|
||||
""")
|
||||
int recoverExpired(@Param("limit") int limit);
|
||||
|
||||
@Update("""
|
||||
UPDATE education_content_import_job
|
||||
SET status = 'COMPLETED', result_summary = CAST(#{resultSummary} AS JSONB),
|
||||
locked_by = NULL, lease_token = NULL, lease_expires_at = NULL, last_heartbeat_at = NULL,
|
||||
finished_at = CURRENT_TIMESTAMP, update_time = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId} AND id = #{id} AND status = 'PROCESSING'
|
||||
AND lease_token = #{leaseToken} AND lease_expires_at > CURRENT_TIMESTAMP
|
||||
""")
|
||||
int complete(@Param("tenantId") Long tenantId, @Param("id") Long id,
|
||||
@Param("leaseToken") String leaseToken, @Param("resultSummary") String resultSummary);
|
||||
|
||||
@Update("UPDATE education_content_import_job SET lease_expires_at = CURRENT_TIMESTAMP - INTERVAL '1 second' WHERE id = #{id}")
|
||||
int expireLeaseForTest(@Param("id") Long id);
|
||||
}
|
||||
@@ -126,26 +126,9 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode QUESTION_COLLECTION_MEMBERSHIP_INVALID = new ErrorCode(1_005_002_035, "题集成员必须是当前租户已发布题目");
|
||||
ErrorCode QUESTION_COLLECTION_MEMBERSHIP_TOO_LARGE = new ErrorCode(1_005_002_036, "题集成员数量不能超过 {}");
|
||||
|
||||
// ========== 商业化与权益 1-005-004-000 ~ 1-005-004-019 ==========
|
||||
ErrorCode EDUCATION_RESOURCE_ACCESS_DENIED = new ErrorCode(1_005_004_000, "教育资源不存在或无权访问");
|
||||
ErrorCode ENTITLEMENT_IDEMPOTENCY_CONFLICT = new ErrorCode(1_005_004_001, "权益事件幂等键已被不同请求使用");
|
||||
ErrorCode ENTITLEMENT_EVENT_INVALID = new ErrorCode(1_005_004_002, "权益事件无效");
|
||||
ErrorCode ENTITLEMENT_RESOURCE_NOT_FOUND = new ErrorCode(1_005_004_003, "权益资源不存在或不可发放");
|
||||
ErrorCode PRODUCT_BINDING_CONFLICT = new ErrorCode(1_005_004_004, "资源商品绑定冲突");
|
||||
ErrorCode PRODUCT_NOT_AVAILABLE = new ErrorCode(1_005_004_005, "Mall 商品不存在或不可用");
|
||||
// ========== 分类创作 1-005-002-040 ~ 1-005-002-049 ==========
|
||||
ErrorCode CATEGORY_AUTHORING_PROVIDER_UNSUPPORTED = new ErrorCode(1_005_002_040,
|
||||
"当前题库数据源模式不支持分类创作:{}");
|
||||
ErrorCode CATEGORY_AUTHORING_NOT_FOUND = new ErrorCode(1_005_002_041, "分类不存在或无权管理");
|
||||
ErrorCode CATEGORY_AUTHORING_CONFLICT = new ErrorCode(1_005_002_042, "分类已变化,请刷新后重试");
|
||||
ErrorCode CATEGORY_SUBJECT_UNAVAILABLE = new ErrorCode(1_005_002_043, "分类学科不存在或不可用");
|
||||
|
||||
// ========== 练习蓝图创作 1-005-002-050 ~ 1-005-002-059 ==========
|
||||
ErrorCode PRACTICE_BLUEPRINT_PROVIDER_UNSUPPORTED = new ErrorCode(1_005_002_050,
|
||||
"当前题库数据源模式不支持练习蓝图创作:{}");
|
||||
ErrorCode PRACTICE_BLUEPRINT_AUTHORING_NOT_FOUND = new ErrorCode(1_005_002_051, "练习蓝图不存在或无权管理");
|
||||
ErrorCode PRACTICE_BLUEPRINT_AUTHORING_CONFLICT = new ErrorCode(1_005_002_052, "练习蓝图已变化,请刷新后重试");
|
||||
ErrorCode PRACTICE_BLUEPRINT_TARGET_UNAVAILABLE = new ErrorCode(1_005_002_053, "练习蓝图目标不存在或不可用");
|
||||
ErrorCode PRACTICE_BLUEPRINT_CONFIG_INVALID = new ErrorCode(1_005_002_054, "练习蓝图配置无效");
|
||||
ErrorCode PRACTICE_BLUEPRINT_ELIGIBLE_INSUFFICIENT = new ErrorCode(1_005_002_055, "练习蓝图可用题目不足");
|
||||
// ========== 内容导入持久化 1-005-002-040 ~ 1-005-002-049 ==========
|
||||
ErrorCode CONTENT_IMPORT_JOB_NOT_FOUND = new ErrorCode(1_005_002_040, "内容导入任务不存在或无权访问");
|
||||
ErrorCode CONTENT_IMPORT_JOB_NOT_READY = new ErrorCode(1_005_002_041, "内容导入任务当前状态不可执行");
|
||||
ErrorCode CONTENT_IMPORT_PREVIEW_DUPLICATE = new ErrorCode(1_005_002_042, "内容导入预览键已存在");
|
||||
ErrorCode CONTENT_IMPORT_LEASE_LOST = new ErrorCode(1_005_002_043, "内容导入任务租约已失效");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
-- EDU-011 persistence slice: tenant-owned import assets and durable preview/execute jobs.
|
||||
|
||||
CREATE TABLE education_content_import_asset (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
object_key VARCHAR(512) NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
mime_type VARCHAR(128) NOT NULL,
|
||||
file_size_bytes BIGINT NOT NULL,
|
||||
checksum_sha256 VARCHAR(64) NOT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT ck_education_content_import_asset_size CHECK (file_size_bytes >= 0),
|
||||
CONSTRAINT uk_education_content_import_asset_tenant_id UNIQUE (tenant_id, id),
|
||||
CONSTRAINT uk_education_content_import_asset_object UNIQUE (tenant_id, object_key)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_content_import_asset IS '教育内容导入源文件元数据;文件存储与扫描由 Infra 持有';
|
||||
COMMENT ON COLUMN education_content_import_asset.object_key IS 'Infra 私有对象键,不存储公开 URL';
|
||||
|
||||
CREATE TABLE education_content_import_job (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
asset_id BIGINT NOT NULL,
|
||||
preview_key VARCHAR(128) NOT NULL,
|
||||
import_type VARCHAR(32) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'PREVIEW',
|
||||
attempt_count INTEGER NOT NULL DEFAULT 0,
|
||||
max_attempts INTEGER NOT NULL DEFAULT 3,
|
||||
next_attempt_at TIMESTAMP,
|
||||
locked_by VARCHAR(128),
|
||||
lease_token VARCHAR(64),
|
||||
lease_expires_at TIMESTAMP,
|
||||
last_heartbeat_at TIMESTAMP,
|
||||
started_at TIMESTAMP,
|
||||
finished_at TIMESTAMP,
|
||||
error_message VARCHAR(2000),
|
||||
preview_payload JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
result_summary JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT fk_education_content_import_job_asset_tenant
|
||||
FOREIGN KEY (tenant_id, asset_id)
|
||||
REFERENCES education_content_import_asset (tenant_id, id),
|
||||
CONSTRAINT uk_education_content_import_job_preview UNIQUE (tenant_id, preview_key),
|
||||
CONSTRAINT ck_education_content_import_job_status
|
||||
CHECK (status IN ('PREVIEW', 'PENDING', 'PROCESSING', 'COMPLETED', 'FAILED')),
|
||||
CONSTRAINT ck_education_content_import_job_attempts
|
||||
CHECK (attempt_count >= 0 AND max_attempts > 0 AND attempt_count <= max_attempts),
|
||||
CONSTRAINT ck_education_content_import_job_lease CHECK (
|
||||
(status = 'PROCESSING' AND locked_by IS NOT NULL AND lease_token IS NOT NULL
|
||||
AND lease_expires_at IS NOT NULL AND last_heartbeat_at IS NOT NULL)
|
||||
OR
|
||||
(status <> 'PROCESSING' AND locked_by IS NULL AND lease_token IS NULL
|
||||
AND lease_expires_at IS NULL AND last_heartbeat_at IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_content_import_job IS '教育内容导入预览与执行作业';
|
||||
COMMENT ON COLUMN education_content_import_job.preview_key IS '租户内重复预览幂等键';
|
||||
COMMENT ON COLUMN education_content_import_job.lease_token IS '处理租约 fencing token';
|
||||
|
||||
CREATE INDEX idx_education_content_import_job_ready
|
||||
ON education_content_import_job (next_attempt_at, create_time, id)
|
||||
WHERE status = 'PENDING' AND deleted = false;
|
||||
CREATE INDEX idx_education_content_import_job_expired
|
||||
ON education_content_import_job (lease_expires_at, create_time, id)
|
||||
WHERE status = 'PROCESSING' AND deleted = false;
|
||||
@@ -0,0 +1,107 @@
|
||||
package cn.iocoder.yudao.module.education.dal.mysql.importjob;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.module.education.dal.dataobject.importjob.ContentImportAssetDO;
|
||||
import cn.iocoder.yudao.module.education.dal.dataobject.importjob.ContentImportJobDO;
|
||||
import cn.iocoder.yudao.module.education.test.PostgreSqlDbIntegrationTest;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ContentImportJobMapperPostgreSqlIntegrationTest extends PostgreSqlDbIntegrationTest {
|
||||
|
||||
@Resource(name = "contentImportAssetMapper") private ContentImportAssetMapper assetMapper;
|
||||
@Resource(name = "contentImportJobMapper") private ContentImportJobMapper jobMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
TenantContextHolder.setTenantId(10L);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEnforceTenantBoundJobLifecycleAndLeaseFencing() {
|
||||
ContentImportAssetDO asset = asset(10L, "imports/10/questions.xlsx");
|
||||
assetMapper.insert(asset);
|
||||
|
||||
ContentImportJobDO preview = preview(10L, asset.getId(), "preview-1");
|
||||
assertEquals(1, jobMapper.insertPreviewIgnore(preview));
|
||||
assertEquals(0, jobMapper.insertPreviewIgnore(preview(10L, asset.getId(), "preview-1")));
|
||||
|
||||
ContentImportAssetDO otherTenantAsset = asset(20L, "imports/20/questions.xlsx");
|
||||
assetMapper.insert(otherTenantAsset);
|
||||
assertThrows(DataIntegrityViolationException.class,
|
||||
() -> jobMapper.insertPreviewIgnore(preview(10L, otherTenantAsset.getId(), "cross-tenant")));
|
||||
|
||||
assertEquals(1, jobMapper.executePreview(10L, preview.getId()));
|
||||
assertEquals(0, jobMapper.executePreview(10L, preview.getId()));
|
||||
|
||||
List<ContentImportJobDO> claimed = jobMapper.claimReady("worker-a", "token-a", 60, 1);
|
||||
assertEquals(1, claimed.size());
|
||||
ContentImportJobDO job = claimed.getFirst();
|
||||
assertEquals("PROCESSING", job.getStatus());
|
||||
assertEquals(1, job.getAttemptCount());
|
||||
assertEquals("token-a", job.getLeaseToken());
|
||||
assertTrue(jobMapper.claimReady("worker-b", "token-b", 60, 1).isEmpty());
|
||||
|
||||
LocalDateTime firstExpiry = job.getLeaseExpiresAt();
|
||||
assertEquals(1, jobMapper.heartbeat(10L, job.getId(), "token-a", 120));
|
||||
assertTrue(jobMapper.selectById(job.getId()).getLeaseExpiresAt().isAfter(firstExpiry));
|
||||
assertEquals(0, jobMapper.heartbeat(10L, job.getId(), "stale-token", 120));
|
||||
assertEquals(0, jobMapper.complete(10L, job.getId(), "stale-token", "{}"));
|
||||
|
||||
jobMapper.expireLeaseForTest(job.getId());
|
||||
assertEquals(1, jobMapper.recoverExpired(10));
|
||||
assertEquals("PENDING", jobMapper.selectById(job.getId()).getStatus());
|
||||
|
||||
ContentImportJobDO reclaimed = jobMapper.claimReady("worker-b", "token-b", 60, 1).getFirst();
|
||||
assertEquals(2, reclaimed.getAttemptCount());
|
||||
assertEquals(0, jobMapper.complete(10L, job.getId(), "token-a", "{}"));
|
||||
assertEquals(1, jobMapper.complete(10L, job.getId(), "token-b", "{\"inserted\":1}"));
|
||||
assertEquals("COMPLETED", jobMapper.selectById(job.getId()).getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowOnlyFiveJobStates() {
|
||||
ContentImportAssetDO asset = asset(10L, "imports/10/states.xlsx");
|
||||
assetMapper.insert(asset);
|
||||
ContentImportJobDO job = preview(10L, asset.getId(), "states");
|
||||
job.setStatus("REJECTED");
|
||||
|
||||
assertThrows(DataIntegrityViolationException.class, () -> jobMapper.insert(job));
|
||||
}
|
||||
|
||||
private ContentImportAssetDO asset(Long tenantId, String objectKey) {
|
||||
ContentImportAssetDO asset = new ContentImportAssetDO();
|
||||
asset.setTenantId(tenantId);
|
||||
asset.setObjectKey(objectKey);
|
||||
asset.setFileName("questions.xlsx");
|
||||
asset.setMimeType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
asset.setFileSizeBytes(128L);
|
||||
asset.setChecksumSha256("a".repeat(64));
|
||||
return asset;
|
||||
}
|
||||
|
||||
private ContentImportJobDO preview(Long tenantId, Long assetId, String previewKey) {
|
||||
ContentImportJobDO job = new ContentImportJobDO();
|
||||
job.setTenantId(tenantId);
|
||||
job.setAssetId(assetId);
|
||||
job.setPreviewKey(previewKey);
|
||||
job.setImportType("QUESTIONS");
|
||||
job.setStatus("PREVIEW");
|
||||
job.setMaxAttempts(3);
|
||||
job.setPreviewPayload("{}");
|
||||
return job;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT COALESCE(version, 'BASELINE') FROM flyway_schema_history ORDER BY installed_rank"))
|
||||
.containsExactly("4009", "4010", "4020", "4030", "4040", "4050", "4060", "4070", "4080", "4090", "4100", "4110", "4120", "4140", "4150", "4160", "4170");
|
||||
.containsExactly("4009", "4010", "4020", "4030", "4040", "4050", "4060", "4070", "4080", "4090", "4100", "4110", "4120", "4130", "4140", "4150", "4160", "4170");
|
||||
assertThat(queryLong(schema,
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = current_schema() " +
|
||||
"AND table_name = 'education_idempotency'"))
|
||||
@@ -211,7 +211,7 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT version FROM flyway_schema_history WHERE success = TRUE ORDER BY installed_rank"))
|
||||
.containsExactly("4010", "4020", "4030", "4040", "4050", "4060", "4070", "4080", "4090", "4100", "4110", "4120", "4140", "4150", "4160", "4170");
|
||||
.containsExactly("4010", "4020", "4030", "4040", "4050", "4060", "4070", "4080", "4090", "4100", "4110", "4120", "4130", "4140", "4150", "4160", "4170");
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT table_name FROM information_schema.tables " +
|
||||
"WHERE table_schema = current_schema() AND table_name IN (" +
|
||||
|
||||
@@ -12,6 +12,8 @@ TRUNCATE TABLE
|
||||
education_category_lifecycle_audit,
|
||||
education_practice_blueprint,
|
||||
education_category,
|
||||
education_content_import_job,
|
||||
education_content_import_asset,
|
||||
education_question_collection_lifecycle_audit,
|
||||
education_content_node_lifecycle_audit,
|
||||
education_question_lifecycle_transition_token,
|
||||
|
||||
Reference in New Issue
Block a user