Mark new regular and chunked uploads as pending and claim persisted article references, including partial saves and large reference sets. Protect associated media from cleanup and add upload lifecycle regression coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
160 lines
9.0 KiB
JavaScript
160 lines
9.0 KiB
JavaScript
'use strict';
|
|
|
|
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 frontSubmit = read('src/Core/FrontSubmit.php');
|
|
const mediaTrait = read('src/Core/FrontSubmitMedia.php');
|
|
const saveTrait = read('src/Core/FrontSubmitSave.php');
|
|
const page = read('templates/pages/page-submit.php');
|
|
const editor = read('assets/js/front-submit-editor.js');
|
|
const submit = read('assets/js/front-submit.js');
|
|
const payDownload = read('assets/js/front-submit-pay-download.js');
|
|
const domainModel = read('src/Core/DomainModel.php');
|
|
const featured = read('src/Core/FrontSubmitFeatured.php');
|
|
const frontSubmitPay = read('src/Core/FrontSubmitPay.php');
|
|
|
|
const runtimeFiles = [
|
|
'assets/js/runtime/main.js',
|
|
'assets/js/runtime/main.min.js',
|
|
];
|
|
|
|
test('front submit owns the typed media REST routes and media trait wiring', () => {
|
|
assert.match(frontSubmit, /use FrontSubmitMedia\s*;/);
|
|
assert.match(frontSubmit, /add_action\('rest_api_init', \[\$this, 'register_rest_routes'\]\)/);
|
|
assert.doesNotMatch(frontSubmit, /add_action\('aksrt_new_(?:add|edit)_posts', \[\$this, 'sync_post_attachments'\]\)/);
|
|
assert.match(saveTrait, /\$this->sync_post_attachments\(\$post\);\s*if \(!\$pay_result\['success'\]\)/);
|
|
|
|
assert.match(frontSubmit, /register_rest_route\('aksrt\/v1', '\/front-submit\/media\/\(\?P<type>image\|video\|file\)'/);
|
|
assert.match(frontSubmit, /'methods'\s*=>\s*\\WP_REST_Server::READABLE/);
|
|
assert.match(frontSubmit, /'callback'\s*=>\s*\[\$this, 'rest_list_attachments'\]/);
|
|
assert.match(frontSubmit, /'post_id'\s*=>\s*\[/);
|
|
assert.match(frontSubmit, /'permission_callback'\s*=>\s*\[\$this, 'can_list_attachments'\]/);
|
|
|
|
assert.match(frontSubmit, /register_rest_route\('aksrt\/v1', '\/front-submit\/upload\/\(\?P<kind>image\|video\|resource\)'/);
|
|
assert.match(frontSubmit, /'methods'\s*=>\s*'POST'/);
|
|
assert.match(frontSubmit, /'callback'\s*=>\s*\[\$this, 'rest_upload_attachment'\]/);
|
|
assert.match(frontSubmit, /'permission_callback'\s*=>\s*\[\$this, 'can_upload_attachment'\]/);
|
|
});
|
|
|
|
test('front submit media listing freezes ownership, visibility, ordering, paging, search, and MIME semantics', () => {
|
|
assert.match(mediaTrait, /\$user_id\s*=\s*get_current_user_id\(\)/);
|
|
assert.match(mediaTrait, /'post_type'\s*=>\s*'attachment'/);
|
|
assert.match(mediaTrait, /'post_status'\s*=>\s*\['inherit', 'private'\]/);
|
|
assert.match(mediaTrait, /'author'\s*=>\s*\$user_id/);
|
|
assert.match(mediaTrait, /'posts_per_page'\s*=>\s*48/);
|
|
assert.match(mediaTrait, /'paged'\s*=>\s*\$page/);
|
|
assert.match(mediaTrait, /'orderby'\s*=>\s*'ID'/);
|
|
assert.match(mediaTrait, /'order'\s*=>\s*'DESC'/);
|
|
assert.match(mediaTrait, /\$request->get_param\('search'\)/);
|
|
assert.match(mediaTrait, /\$request->get_param\('exclude'\)/);
|
|
assert.match(mediaTrait, /\$args\['post__not_in'\]\s*=\s*\$exclude/);
|
|
assert.match(mediaTrait, /\$args\['s'\]\s*=\s*\$search/);
|
|
assert.match(mediaTrait, /add_filter\('wp_allow_query_attachment_by_filename', '__return_true'\)/);
|
|
assert.match(mediaTrait, /remove_filter\('wp_allow_query_attachment_by_filename', '__return_true'\)/);
|
|
assert.match(mediaTrait, /\$args\['post_mime_type'\]\s*=\s*'image'/);
|
|
assert.match(mediaTrait, /\$args\['post_mime_type'\]\s*=\s*'video'/);
|
|
assert.doesNotMatch(mediaTrait, /\$args\['post_mime_type'\]\s*=\s*'file'/);
|
|
assert.match(mediaTrait, /return \$mime !== '';/);
|
|
assert.match(mediaTrait, /!in_array\(\$type, \['image', 'video', 'file'\], true\)/);
|
|
assert.match(frontSubmit, /\['image', 'video', 'file'\]/);
|
|
});
|
|
|
|
test('front submit media responses and uploads use the attachment contract', () => {
|
|
for (const field of ['attachment_id', 'id', 'url', 'filename', 'type', 'subtype', 'thumbnail_url', 'filesizeInBytes']) {
|
|
assert.match(mediaTrait, new RegExp(`['"]${field}['"]`), `missing normalized field ${field}`);
|
|
}
|
|
assert.match(mediaTrait, /wp_prepare_attachment_for_js/);
|
|
assert.match(mediaTrait, /'attachment_id'\s*=>\s*\(int\) \$attachment->ID/);
|
|
assert.match(mediaTrait, /'id'\s*=>\s*\(int\) \$attachment->ID/);
|
|
assert.match(mediaTrait, /'thumbnail_url'\s*=>/);
|
|
assert.match(mediaTrait, /'filesizeInBytes'\s*=>/);
|
|
|
|
assert.match(frontSubmit, /\$files\['file'\]/);
|
|
assert.match(frontSubmit, /media_handle_upload\('aksrt_front_submit_file'/);
|
|
assert.match(frontSubmit, /update_post_meta\(\$attachment_id, '_aksrt_front_submit_media', \$kind\)/);
|
|
assert.match(frontSubmit, /\$attachment\['success'\]\s*=\s*true/);
|
|
assert.match(editor, /body\.append\('file', file\)/);
|
|
assert.match(editor, /method:\s*'POST'/);
|
|
assert.match(editor, /payload\.success/);
|
|
});
|
|
|
|
test('saved submission media sync enforces ownership and cleans stale parent/meta relations', () => {
|
|
assert.match(mediaTrait, /if \(\$user_id <= 0 \|\| \(int\) \$post->post_author !== \$user_id\)/);
|
|
assert.match(mediaTrait, /'post_parent'\s*=>\s*0/);
|
|
assert.match(mediaTrait, /\(int\) \$attachment->post_author !== \$user_id/);
|
|
assert.match(mediaTrait, /\['inherit', 'private'\]/);
|
|
assert.match(mediaTrait, /\(int\) \$attachment->post_parent !== 0/);
|
|
assert.match(mediaTrait, /'meta_key'\s*=>\s*'_aksrt_front_submit_media'/);
|
|
assert.match(mediaTrait, /delete_post_meta\(\$attachment_id, '_aksrt_front_submit_media'\)/);
|
|
assert.match(mediaTrait, /update_post_meta\(\$attachment_id, '_aksrt_front_submit_media', \$kind\)/);
|
|
assert.match(mediaTrait, /featured_video_attachment_id/);
|
|
assert.match(mediaTrait, /data-aksrt-file-id/);
|
|
assert.match(mediaTrait, /\['post_parent'\s*=>\s*\$post_id\]/);
|
|
});
|
|
|
|
test('submit page configures media URLs, REST nonce, and the three media copy groups without mini-upload', () => {
|
|
assert.match(page, /'restNonce'\s*=>\s*wp_create_nonce\('wp_rest'\)/);
|
|
assert.match(page, /'mediaUrl'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media'\)\)/);
|
|
assert.match(page, /'image'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/image'\)\)\]/);
|
|
assert.match(page, /'video'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/video'\)\)\]/);
|
|
assert.match(page, /'file'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/file'\)\)\]/);
|
|
for (const copy of ['mineImage', 'mineVideo', 'mineFile', 'uploadImage', 'uploadVideo', 'uploadFile']) {
|
|
assert.match(page, new RegExp(`['"]${copy}['"]`), `missing copy ${copy}`);
|
|
}
|
|
assert.doesNotMatch(page, /aksrt-mini-upload/);
|
|
assert.equal(fs.existsSync(path.join(root, 'assets/js/mini-upload.js')), false, 'mini-upload.js must stay removed');
|
|
});
|
|
|
|
test('shared submit media picker exposes mine search paging selection and nonce-backed upload', () => {
|
|
assert.match(editor, /window\.AksrtSubmitMedia\s*=\s*\{\s*open:/);
|
|
assert.match(editor, /data-editor-media-tab="mine"/);
|
|
assert.match(editor, /data-editor-media-search/);
|
|
assert.match(editor, /state\.search\.length >= 2/);
|
|
assert.match(editor, /state\.search\.length === 1/);
|
|
assert.match(editor, /state\.searchTimer = window\.setTimeout/);
|
|
assert.match(editor, /state\.searchTimer[\s\S]*?600\)/);
|
|
assert.match(editor, /params\.set\('paged', String\(page\)\)/);
|
|
assert.match(editor, /data-editor-media-more/);
|
|
assert.match(editor, /params\.set\('exclude', state\.exclude\.join\(','\)\)/);
|
|
assert.match(editor, /multiple\s*=\s*options\.multiple/);
|
|
assert.match(editor, /if \(!multiple\) maxItems = 1/);
|
|
assert.match(editor, /state\.selected\.length >= maxItems/);
|
|
assert.match(editor, /headers:\s*\{\s*'X-WP-Nonce': cfg\.restNonce \|\| ''\s*\}/);
|
|
assert.match(editor, /data-aksrt-file-id/);
|
|
assert.doesNotMatch(editor, /data-download-file/);
|
|
assert.match(submit, /data-aksrt-submit-ready/);
|
|
});
|
|
|
|
test('paid downloads and featured covers use the shared picker and attachment IDs', () => {
|
|
assert.match(payDownload, /window\.AksrtSubmitMedia\.open\(/);
|
|
assert.match(payDownload, /type:\s*'file'/);
|
|
assert.match(payDownload, /item\.attachment_id \|\| item\.id/);
|
|
assert.match(payDownload, /data-pay-resource-field="attachment_id"/);
|
|
assert.match(domainModel, /'attachment_id'\s*=>\s*is_scalar\(\$row\['attachment_id'\]/);
|
|
assert.match(domainModel, /max\(0, \(int\) \$row\['attachment_id'\]\)/);
|
|
|
|
assert.match(submit, /openMediaPicker:\s*function \(options\)/);
|
|
assert.match(submit, /window\.AksrtSubmitMedia\.open\(options\)/);
|
|
assert.match(submit, /type:\s*'image'/);
|
|
assert.match(submit, /type:\s*'video'/);
|
|
assert.match(submit, /item\.attachment_id \|\| item\.id/);
|
|
assert.match(domainModel, /'featured_video_attachment_id'\s*=>\s*0/);
|
|
assert.match(payDownload, /attachment_id/);
|
|
assert.match(frontSubmitPay, /\$meta\['downloads'\]\s*=\s*\$downloads/);
|
|
assert.match(featured, /featured_video_attachment_id/);
|
|
});
|
|
|
|
test('runtime bundles contain no legacy download-file action handler', () => {
|
|
for (const file of runtimeFiles) {
|
|
const source = read(file);
|
|
assert.doesNotMatch(source, /action=download_file/);
|
|
assert.doesNotMatch(source, /download-nonce/);
|
|
}
|
|
});
|