TikTok V6

This commit is contained in:
2026-07-13 18:52:14 +02:00
parent 9ebe223873
commit 5c09c50af9
9 changed files with 287 additions and 29 deletions

View File

@@ -1,7 +1,9 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { isAllowedR2MediaUrl, isOwnedTiktokMediaUrl, prepareTiktokPhotoUrls } = require('../lib/tiktok-assets');
const { isAllowedR2MediaUrl, isOwnedTiktokMediaUrl, prepareTiktokPhotoUrls, verifyPublicPhotoUrl } = require('../lib/tiktok-assets');
const verifyPhotoUrl = async () => {};
const publicBaseUrl = 'https://greenlenspro.com/storage';
@@ -17,6 +19,7 @@ test('keeps existing GreenLens storage URLs without uploading them again', async
const url = 'https://greenlenspro.com/storage/plant-images/slide.jpg';
const result = await prepareTiktokPhotoUrls([url], {
publicBaseUrl,
verifyPhotoUrl,
uploadImage: async () => { uploads += 1; },
});
@@ -28,6 +31,7 @@ test('hosts base64 photos on the verified GreenLens domain', async () => {
const uploaded = [];
const result = await prepareTiktokPhotoUrls([{ imageBase64: Buffer.from('source').toString('base64') }], {
publicBaseUrl,
verifyPhotoUrl,
normalizePhoto: async (buffer) => {
assert.equal(buffer.toString(), 'source');
return Buffer.from('jpeg');
@@ -54,6 +58,7 @@ test('rehosts an external R2 photo instead of leaking its URL into the TikTok pa
const sourceUrl = 'https://pub-example.r2.dev/slide.png';
const result = await prepareTiktokPhotoUrls([sourceUrl], {
publicBaseUrl,
verifyPhotoUrl,
downloadPhoto: async (url) => {
assert.equal(url, sourceUrl);
return Buffer.from('png');
@@ -73,6 +78,7 @@ test('prefers supplied base64 over an external legacy URL', async () => {
imageBase64: Buffer.from('local').toString('base64'),
}], {
publicBaseUrl,
verifyPhotoUrl,
downloadPhoto: async () => { downloaded = true; },
normalizePhoto: async (buffer) => {
assert.equal(buffer.toString(), 'local');
@@ -89,9 +95,56 @@ test('rejects a storage result outside the verified GreenLens domain', async ()
await assert.rejects(
prepareTiktokPhotoUrls([{ imageBase64: Buffer.from('source').toString('base64') }], {
publicBaseUrl,
verifyPhotoUrl,
normalizePhoto: async () => Buffer.from('jpeg'),
uploadImage: async () => ({ url: 'https://pub-example.r2.dev/slide.jpg' }),
}),
/unverified public URL/,
);
});
const okHeaders = (extra = {}) => ({
get: (name) => ({ 'content-type': 'image/jpeg', 'content-length': '1024', ...extra }[name.toLowerCase()] ?? null),
});
test('verifyPublicPhotoUrl accepts a reachable JPEG under 20 MB', async () => {
await verifyPublicPhotoUrl('https://greenlenspro.com/storage/a.jpg', {
fetchImpl: async () => ({ status: 200, headers: okHeaders() }),
});
});
test('verifyPublicPhotoUrl retries transient 404s before failing', async () => {
let calls = 0;
await verifyPublicPhotoUrl('https://greenlenspro.com/storage/a.jpg', {
fetchImpl: async () => {
calls += 1;
return calls < 3
? { status: 404, headers: okHeaders() }
: { status: 200, headers: okHeaders() };
},
});
assert.equal(calls, 3);
});
test('verifyPublicPhotoUrl rejects unsupported content types without retrying', async () => {
let calls = 0;
await assert.rejects(
verifyPublicPhotoUrl('https://greenlenspro.com/storage/a.gif', {
fetchImpl: async () => {
calls += 1;
return { status: 200, headers: { get: (n) => (n.toLowerCase() === 'content-type' ? 'image/gif' : null) } };
},
}),
/unsupported content type/,
);
assert.equal(calls, 1);
});
test('verifyPublicPhotoUrl rejects photos larger than 20 MB', async () => {
await assert.rejects(
verifyPublicPhotoUrl('https://greenlenspro.com/storage/a.jpg', {
fetchImpl: async () => ({ status: 200, headers: okHeaders({ 'content-length': String(21 * 1024 * 1024) }) }),
}),
/larger than 20 MB/,
);
});