mirror of
https://github.com/misenhower/splatoon2.ink.git
synced 2026-09-28 12:16:54 -05:00
227 lines
7.1 KiB
JavaScript
227 lines
7.1 KiB
JavaScript
import { test, beforeEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import sharp from 'sharp';
|
|
import SocialPostBase from '../../src/app/social/posts/SocialPostBase.js';
|
|
import { storage, fakeClient, json, seed } from './support.js';
|
|
|
|
const IMAGE = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
|
|
|
|
class HourlyPost extends SocialPostBase {
|
|
constructor(s, clients, { data = { id: 1 }, time = 3600, publicImage = 'hourly.png' } = {}) {
|
|
super(s, clients);
|
|
this.data = data;
|
|
this.time = time;
|
|
this.publicImage = publicImage;
|
|
this.images = 0;
|
|
}
|
|
getKey() {
|
|
return 'hourly';
|
|
}
|
|
getName() {
|
|
return 'Hourly';
|
|
}
|
|
async getDataTime() {
|
|
return this.time;
|
|
}
|
|
async getData() {
|
|
return this.data;
|
|
}
|
|
async getImage() {
|
|
this.images++;
|
|
|
|
return IMAGE;
|
|
}
|
|
getPublicImageFilename() {
|
|
return this.publicImage;
|
|
}
|
|
async getText(data) {
|
|
return `Post ${data.id}`;
|
|
}
|
|
}
|
|
|
|
let s, bluesky, other;
|
|
|
|
beforeEach(() => {
|
|
s = storage();
|
|
bluesky = fakeClient('bluesky');
|
|
other = fakeClient('other');
|
|
});
|
|
|
|
test('posts to every client, saves the public image, and records the time per client', async () => {
|
|
const post = new HourlyPost(s, [bluesky, other]);
|
|
|
|
assert.equal((await post.maybePost()).ok, true);
|
|
|
|
assert.deepEqual(bluesky.sent, [{ status: 'Post 1', media: [{ file: IMAGE, type: 'image/png' }] }]);
|
|
assert.deepEqual(other.sent, bluesky.sent);
|
|
assert.equal(post.images, 1);
|
|
|
|
const publicImage = await s.publicBucket.get('twitter-images/hourly.png');
|
|
|
|
assert.equal(publicImage.httpMetadata.cacheControl, 'no-cache');
|
|
assert.deepEqual(
|
|
new Uint8Array(await publicImage.arrayBuffer()),
|
|
IMAGE,
|
|
);
|
|
assert.deepEqual(await json(s.privateBucket, 'bluesky-lastPostTimes.json'), { hourly: 3600 });
|
|
assert.deepEqual(await json(s.privateBucket, 'other-lastPostTimes.json'), { hourly: 3600 });
|
|
});
|
|
|
|
test('does not post the same data time twice, and posts again when it moves on', async () => {
|
|
await seed(s.privateBucket, 'bluesky-lastPostTimes.json', { hourly: 3600, other: 99 });
|
|
|
|
const post = new HourlyPost(s, [bluesky]);
|
|
|
|
assert.equal(await post.maybePost(), false);
|
|
assert.deepEqual(bluesky.sent, []);
|
|
assert.equal(post.images, 0);
|
|
|
|
post.time = 7200;
|
|
await post.maybePost();
|
|
|
|
assert.equal(bluesky.sent.length, 1);
|
|
assert.deepEqual(await json(s.privateBucket, 'bluesky-lastPostTimes.json'), { hourly: 7200, other: 99 });
|
|
});
|
|
|
|
test('a client that already posted is skipped while another still posts', async () => {
|
|
await seed(s.privateBucket, 'bluesky-lastPostTimes.json', { hourly: 3600 });
|
|
await new HourlyPost(s, [bluesky, other]).maybePost();
|
|
|
|
assert.deepEqual(bluesky.sent, []);
|
|
assert.equal(other.sent.length, 1);
|
|
});
|
|
|
|
test('a failing client does not record a post time and does not block the other client', async () => {
|
|
const broken = fakeClient('bluesky', { fail: true });
|
|
const result = await new HourlyPost(s, [broken, other]).maybePost();
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.deepEqual(
|
|
result.clients.map(c => c.ok),
|
|
[false, true],
|
|
);
|
|
assert.equal(other.sent.length, 1);
|
|
assert.equal(await json(s.privateBucket, 'bluesky-lastPostTimes.json'), null);
|
|
assert.deepEqual(await json(s.privateBucket, 'other-lastPostTimes.json'), { hourly: 3600 });
|
|
});
|
|
|
|
test('captures one PNG and converts it once for JPEG clients, preserving dimensions', async () => {
|
|
const png = await sharp({ create: { width: 2, height: 1, channels: 3, background: '#ff0000' } })
|
|
.png()
|
|
.toBuffer();
|
|
class ScreenshotPost extends HourlyPost {
|
|
async getImage() {
|
|
this.images++;
|
|
|
|
return { image: png, type: 'image/png', width: 2, height: 1 };
|
|
}
|
|
}
|
|
bluesky.mediaType = 'image/jpeg';
|
|
|
|
const secondJpeg = fakeClient('second');
|
|
secondJpeg.mediaType = 'image/jpeg';
|
|
|
|
const post = new ScreenshotPost(s, [bluesky, other, secondJpeg]);
|
|
|
|
assert.equal((await post.maybePost()).ok, true);
|
|
assert.equal(post.images, 1);
|
|
|
|
const jpeg = bluesky.sent[0].media[0];
|
|
|
|
assert.equal(jpeg.type, 'image/jpeg');
|
|
assert.equal(jpeg.width, 2);
|
|
assert.equal(jpeg.height, 1);
|
|
|
|
const decoded = await sharp(jpeg.file).metadata();
|
|
|
|
assert.equal(decoded.format, 'jpeg');
|
|
assert.equal(decoded.width, 2);
|
|
assert.equal(decoded.height, 1);
|
|
assert.equal(secondJpeg.sent[0].media[0], jpeg);
|
|
assert.deepEqual(other.sent[0].media[0].file, png);
|
|
assert.deepEqual(
|
|
new Uint8Array(await (await s.publicBucket.get('twitter-images/hourly.png')).arrayBuffer()),
|
|
new Uint8Array(png),
|
|
);
|
|
});
|
|
|
|
test('conversion failure preserves the PNG and does not checkpoint or send the JPEG post', async () => {
|
|
bluesky.mediaType = 'image/jpeg';
|
|
|
|
const post = new HourlyPost(s, [bluesky, other]);
|
|
post.convertMedia = async () => {
|
|
throw new Error('Images quota exceeded');
|
|
};
|
|
|
|
const result = await post.maybePost();
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.clients[0].error, /Images quota exceeded/);
|
|
assert.equal(bluesky.sent.length, 0);
|
|
assert.equal(other.sent.length, 1);
|
|
assert.equal(post.images, 1);
|
|
assert.ok(await s.publicBucket.get('twitter-images/hourly.png'));
|
|
assert.equal(await json(s.privateBucket, 'bluesky-lastPostTimes.json'), null);
|
|
});
|
|
|
|
test('with no data there is nothing to post', async () => {
|
|
const post = new HourlyPost(s, [bluesky], { data: null });
|
|
|
|
assert.equal(await post.maybePost(), false);
|
|
assert.equal(post.images, 0);
|
|
});
|
|
|
|
test('with no client able to send, the public image is still saved', async () => {
|
|
const offline = fakeClient('bluesky', { canSend: false });
|
|
|
|
await new HourlyPost(s, [offline]).maybePost();
|
|
|
|
assert.deepEqual(offline.sent, []);
|
|
assert.ok(await s.publicBucket.get('twitter-images/hourly.png'));
|
|
});
|
|
|
|
test('with no client able to send and no public image, it gives up', async () => {
|
|
const offline = fakeClient('bluesky', { canSend: false });
|
|
const post = new HourlyPost(s, [offline], { publicImage: null });
|
|
|
|
assert.equal(await post.maybePost(), false);
|
|
assert.equal(post.images, 0);
|
|
});
|
|
|
|
test('test screenshots go to public storage', async () => {
|
|
await new HourlyPost(s, []).saveTestScreenshot();
|
|
|
|
assert.deepEqual(
|
|
new Uint8Array(await (await s.publicBucket.get('test-screenshots/hourly.png')).arrayBuffer()),
|
|
IMAGE,
|
|
);
|
|
});
|
|
|
|
test('screenshot failure is reported and does not advance post state', async () => {
|
|
const post = new HourlyPost(s, [bluesky]);
|
|
post.getImage = async () => {
|
|
throw new Error('render failed');
|
|
};
|
|
|
|
const result = await post.maybePost();
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.error, /render failed/);
|
|
assert.equal(await json(s.privateBucket, 'bluesky-lastPostTimes.json'), null);
|
|
});
|
|
|
|
test('retry only sends the previously failed client, using a fresh storage view', async () => {
|
|
const broken = fakeClient('other', { fail: true });
|
|
const first = await new HourlyPost(s, [bluesky, broken]).maybePost();
|
|
|
|
assert.equal(first.ok, false);
|
|
|
|
const recovered = fakeClient('other');
|
|
const fresh = { ...s, privateStorage: new s.privateStorage.constructor(s.privateBucket) };
|
|
const second = await new HourlyPost(fresh, [bluesky, recovered]).maybePost();
|
|
|
|
assert.equal(second.ok, true);
|
|
assert.equal(bluesky.sent.length, 1);
|
|
assert.equal(recovered.sent.length, 1);
|
|
});
|