Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.
Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)
Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
(dropped 3 stale entries, added the 17 missing ones)
Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
when it is empty
Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.
Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
128 lines
6.4 KiB
JavaScript
128 lines
6.4 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* 工单表结构契约:
|
||
* 1) 6 张 jio_ticket* 表全部由 DBMigratorPart2 建出,且被 migrate_ticket_tables() 调用;
|
||
* 2) SCHEMA_VERSION 已升到 3.3.0(dbDelta 只在版本变化时重跑);
|
||
* 3) jio_order_aftersale 已从 schema 与表名映射中彻底移除;
|
||
* 4) 状态与部门 seeds 幂等,默认状态被标记为不可改删。
|
||
*/
|
||
|
||
const assert = require('node:assert/strict');
|
||
const fs = require('node:fs');
|
||
const path = require('node:path');
|
||
const test = require('node:test');
|
||
|
||
const root = path.resolve(__dirname, '../..');
|
||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||
|
||
const TABLES = [
|
||
'jio_ticket',
|
||
'jio_ticket_reply',
|
||
'jio_ticket_status',
|
||
'jio_ticket_type',
|
||
'jio_ticket_prereply',
|
||
'jio_ticket_note',
|
||
];
|
||
|
||
test('schema version is bumped so dbDelta re-runs', () => {
|
||
const migrator = read('src/Core/DBMigrator.php');
|
||
assert.match(migrator, /const SCHEMA_VERSION = '3\.3\.0';/, 'SCHEMA_VERSION must be 3.3.0');
|
||
assert.ok(migrator.includes('use DBMigratorTicket;'), 'DBMigratorTicket trait not composed');
|
||
// 版本注释要说明每次升版做了什么,否则后人看不懂升版原因。
|
||
assert.match(migrator, /3\.0\.0: 售后(order_aftersale)整体替换为工单系统/);
|
||
assert.match(migrator, /3\.1\.0: jio_ticket 新增 urge_count \/ last_urge_time/);
|
||
// 3.3.0 的品牌重命名(aksrt_ → jio_ 表前缀)也必须写进注释。
|
||
assert.match(migrator, /3\.3\.0: 主题品牌由 AKSRT 更名为 Jio/);
|
||
const ticket = read('src/Core/DBMigratorTicket.php');
|
||
assert.match(ticket, /urge_count INT UNSIGNED NOT NULL DEFAULT 0,/);
|
||
assert.match(ticket, /last_urge_time DATETIME DEFAULT NULL,/);
|
||
});
|
||
|
||
test('order_aftersale is gone from schema and the table map', () => {
|
||
const part2 = read('src/Core/DBMigratorPart2.php');
|
||
const part3 = read('src/Core/DBMigratorPart3.php');
|
||
|
||
assert.ok(!part2.includes('jio_order_aftersale'), 'order_aftersale CREATE must be removed');
|
||
assert.ok(!part3.includes("'order_aftersale'"), 'order_aftersale table map entry must be removed');
|
||
assert.ok(!part3.includes('jio_order_aftersale'), 'order_aftersale table name must be removed');
|
||
});
|
||
|
||
test('six ticket tables are declared and the migrator is invoked', () => {
|
||
const part2 = read('src/Core/DBMigratorPart2.php');
|
||
// Part2 只负责调用;DDL 集中在 DBMigratorTicket,避免 run() 继续膨胀。
|
||
assert.ok(part2.includes('self::migrate_ticket_tables($prefix, $charset_collate);'),
|
||
'migrate_ticket_tables() must be called from run()');
|
||
assert.ok(!part2.includes('jio_order_aftersale'), 'Part2 must not carry the old table');
|
||
|
||
const ticket = read('src/Core/DBMigratorTicket.php');
|
||
for (const table of TABLES) {
|
||
assert.ok(ticket.includes(`CREATE TABLE {$prefix}${table} (`), `${table} DDL missing in trait`);
|
||
}
|
||
assert.ok(ticket.includes('self::seed_ticket_statuses($prefix);'), 'status seeds not invoked');
|
||
assert.ok(ticket.includes('self::seed_ticket_types($prefix);'), 'type seeds not invoked');
|
||
});
|
||
|
||
test('ticket table map exposes all six names', () => {
|
||
const part3 = read('src/Core/DBMigratorPart3.php');
|
||
for (const name of ['ticket', 'ticket_reply', 'ticket_status', 'ticket_type', 'ticket_prereply', 'ticket_note']) {
|
||
assert.ok(part3.includes(`'${name}'`), `table map missing ${name}`);
|
||
assert.ok(part3.includes(`'jio_${name}'`), `table map missing jio_${name}`);
|
||
}
|
||
});
|
||
|
||
test('ticket columns cover the state machine and optional order link', () => {
|
||
const ticket = read('src/Core/DBMigratorTicket.php');
|
||
|
||
// 工单号对外唯一;状态是可自定义的 key;订单关联可空(默认空串)。
|
||
assert.match(ticket, /UNIQUE KEY uniq_ticket_num \(ticket_num\)/);
|
||
assert.match(ticket, /status VARCHAR\(24\) NOT NULL DEFAULT 'pending'/);
|
||
assert.match(ticket, /order_number VARCHAR\(32\) NOT NULL DEFAULT ''/);
|
||
assert.match(ticket, /KEY idx_order \(order_number\)/);
|
||
|
||
// 沟通记录独立成行,支持引用回复。
|
||
assert.match(ticket, /author_type VARCHAR\(16\) NOT NULL DEFAULT 'user'/);
|
||
assert.match(ticket, /quote_reply_id BIGINT UNSIGNED NOT NULL DEFAULT 0/);
|
||
|
||
// 状态表带完结/默认两个开关,默认状态不可改删由服务层兜住。
|
||
assert.match(ticket, /status_key VARCHAR\(24\) NOT NULL/);
|
||
assert.match(ticket, /UNIQUE KEY uniq_status_key \(status_key\)/);
|
||
assert.match(ticket, /is_closed TINYINT\(1\) NOT NULL DEFAULT 0/);
|
||
assert.match(ticket, /is_default TINYINT\(1\) NOT NULL DEFAULT 0/);
|
||
});
|
||
|
||
test('status and department seeds are idempotent', () => {
|
||
const ticket = read('src/Core/DBMigratorTicket.php');
|
||
|
||
// 按 key / name 查重后跳过,重复执行不会写重复行。
|
||
assert.match(ticket, /\$exists = \$wpdb->get_col\("SELECT status_key FROM \{\$table\}"\)/);
|
||
assert.match(ticket, /if \(in_array\(\$key, \$exists, true\)\) \{\s*\n\s*continue;/);
|
||
assert.match(ticket, /\$exists = \$wpdb->get_col\("SELECT name FROM \{\$table\}"\)/);
|
||
assert.match(ticket, /if \(in_array\(\$label, \$exists, true\)\) \{\s*\n\s*continue;/);
|
||
|
||
// 五个内置状态与五个部门。
|
||
for (const key of ['pending', 'handling', 'user_replied', 'admin_replied', 'closed']) {
|
||
assert.ok(ticket.includes(`['${key}',`), `status seed ${key} missing`);
|
||
}
|
||
for (const label of ['技术支持', '账号问题', '财务问题', '产品建议', '其他']) {
|
||
assert.ok(ticket.includes(`'${label}'`), `department seed ${label} missing`);
|
||
}
|
||
// 默认状态是「待接单」,且是完结态的补集。
|
||
assert.match(ticket, /\['pending', '待接单', '#f59e0b', 0, 1\]/);
|
||
assert.match(ticket, /\['closed', '已关闭', '#6b7280', 1, 0\]/);
|
||
});
|
||
|
||
test('TicketStatusService seeds mirror the migrator and guard the default row', () => {
|
||
const service = read('src/Ticket/TicketStatusService.php');
|
||
for (const key of ['pending', 'handling', 'user_replied', 'admin_replied', 'closed']) {
|
||
assert.ok(service.includes(`KEY_${key.toUpperCase()}`), `KEY_${key.toUpperCase()} missing`);
|
||
}
|
||
// 状态机只挡确定非法的组合,其余交给「目标状态必须存在」。
|
||
assert.match(service, /if \(\$from === self::KEY_PENDING && \$to === self::KEY_CLOSED\) \{\s*\n\s*return false;/);
|
||
assert.match(service, /if \(self::is_closed\(\$from\)\) \{\s*\n\s*return false;/);
|
||
// 颜色只接受 #rgb / #rrggbb。
|
||
assert.match(service, /'#6b7280'/);
|
||
assert.match(service, /\\Jio\\Core\\Color::sanitize\(\$color\)/);
|
||
assert.doesNotMatch(service, /preg_match\('\/\^#/);
|
||
});
|