mirror of
https://github.com/misenhower/splatoon2.ink.git
synced 2026-09-28 04:06:52 -05:00
258 lines
8.8 KiB
JavaScript
258 lines
8.8 KiB
JavaScript
import { test, beforeEach, afterEach, mock } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import SchedulesUpdater from '../../src/app/updater/updaters/SchedulesUpdater.js';
|
|
import CoopSchedulesUpdater from '../../src/app/updater/updaters/CoopSchedulesUpdater.js';
|
|
import TimelineUpdater from '../../src/app/updater/updaters/TimelineUpdater.js';
|
|
import MerchandisesUpdater from '../../src/app/updater/updaters/MerchandisesUpdater.js';
|
|
import FestivalsUpdater from '../../src/app/updater/updaters/FestivalsUpdater.js';
|
|
import OriginalGearImageUpdater from '../../src/app/updater/updaters/OriginalGearImageUpdater.js';
|
|
import { createUpdaters } from '../../src/app/updater/updateAll.js';
|
|
import { getTopOfCurrentHour } from '../../src/common/time.js';
|
|
import gearData from '../../src/common/data/gear.json' with { type: 'json' };
|
|
import skills from '../../src/common/data/skills.json' with { type: 'json' };
|
|
import { fakeSplatNet, buckets, json, keys, setSessionEnvironment } from './support.js';
|
|
|
|
setSessionEnvironment();
|
|
|
|
let b;
|
|
|
|
beforeEach(() => {
|
|
b = buckets();
|
|
});
|
|
afterEach(() => mock.restoreAll());
|
|
|
|
const stage = (id, name) => ({ id, name, image: `/images/stage/${id}.png` });
|
|
const rotation = (start, a, c, rule = 'turf_war') => ({
|
|
id: start,
|
|
start_time: start,
|
|
end_time: start + 7200,
|
|
stage_a: a,
|
|
stage_b: c,
|
|
game_mode: { key: 'regular', name: 'Regular Battle' },
|
|
rule: { key: rule, name: 'Turf War', multiline_name: 'Turf\nWar' },
|
|
});
|
|
|
|
test('createUpdaters builds every updater against the given buckets', () => {
|
|
const updaters = createUpdaters(b);
|
|
|
|
assert.deepEqual(
|
|
updaters.map(u => u.options.name),
|
|
[
|
|
'Original Gear',
|
|
'Schedules',
|
|
'Co-op Schedules',
|
|
'Timeline',
|
|
'Festivals NA',
|
|
'Festivals EU',
|
|
'Festivals JP',
|
|
'Merchandises',
|
|
],
|
|
);
|
|
assert.ok(
|
|
updaters.every(u => u.publicStorage === b.publicStorage && u.privateStorage === b.privateStorage),
|
|
);
|
|
});
|
|
|
|
test('schedules: seeds the known stages from SplatNet and records new stages once', async () => {
|
|
const reef = stage('0', 'The Reef'),
|
|
fitness = stage('1', 'Musselforge Fitness'),
|
|
skipper = stage('22', 'Skipper Pavilion');
|
|
const schedules = {
|
|
regular: [rotation(7200, skipper, reef), rotation(3600, reef, fitness)],
|
|
gachi: [rotation(3600, reef, fitness)],
|
|
league: [rotation(3600, fitness, reef)],
|
|
};
|
|
const splatnet = fakeSplatNet({
|
|
'/api/schedules': () => schedules,
|
|
'/api/data/stages': () => ({ stages: [reef, fitness] }),
|
|
});
|
|
|
|
await new SchedulesUpdater(b).update();
|
|
|
|
assert.deepEqual(await json(b.publicBucket, 'data/schedules.json'), schedules);
|
|
assert.deepEqual(await json(b.privateBucket, 'stages.json'), [
|
|
{ ...reef, first_seen: -1, first_available: -1 },
|
|
{ ...fitness, first_seen: -1, first_available: -1 },
|
|
{ ...skipper, first_seen: getTopOfCurrentHour(), first_available: 7200 },
|
|
]);
|
|
assert.ok(splatnet.api.some(r => r.path === '/api/data/stages'));
|
|
|
|
const second = fakeSplatNet({ '/api/schedules': () => schedules });
|
|
const puts = mock.method(b.privateBucket, 'put');
|
|
|
|
await new SchedulesUpdater(b).update();
|
|
|
|
assert.equal(second.api.filter(r => r.path === '/api/data/stages').length, 0);
|
|
assert.equal(puts.mock.calls.length, 0);
|
|
});
|
|
|
|
test('coop: publishes the calendar with stage and weapon details', async () => {
|
|
const coop = {
|
|
schedules: [
|
|
{ start_time: 3600, end_time: 7200 },
|
|
{ start_time: 90000, end_time: 93600 },
|
|
],
|
|
details: [
|
|
{
|
|
start_time: 3600,
|
|
end_time: 7200,
|
|
stage: { name: 'Spawning Grounds', image: '/images/coop_stage/a.png' },
|
|
weapons: [
|
|
{
|
|
id: '0',
|
|
weapon: {
|
|
id: '0',
|
|
name: 'Splattershot',
|
|
image: '/images/weapon/0.png',
|
|
sub: { id: '1', name: 'Bomb' },
|
|
special: { id: '2', name: 'Special' },
|
|
},
|
|
},
|
|
null,
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
fakeSplatNet({ '/api/coop_schedules': () => coop });
|
|
await new CoopSchedulesUpdater(b).update();
|
|
|
|
const ics = await (await b.publicBucket.get('data/coop-schedules.ics')).text();
|
|
|
|
assert.match(ics, /SUMMARY:Salmon Run on Spawning Grounds/);
|
|
assert.match(ics, /Splattershot/);
|
|
assert.match(ics, /Random/);
|
|
assert.match(ics, /SUMMARY:Salmon Run\r?\n/); // the shift without details keeps the plain title
|
|
assert.deepEqual(await json(b.publicBucket, 'data/coop-schedules.json'), coop);
|
|
});
|
|
|
|
test('timeline: keeps only coop and weapon availability, dropping hidden items', async () => {
|
|
fakeSplatNet({
|
|
'/api/timeline': () => ({
|
|
coop: { importance: -1 },
|
|
weapon_availability: { importance: 1, availabilities: [] },
|
|
stats: { ignored: true },
|
|
}),
|
|
});
|
|
await new TimelineUpdater(b).update();
|
|
|
|
assert.deepEqual(await json(b.publicBucket, 'data/timeline.json'), {
|
|
coop: null,
|
|
weapon_availability: { importance: 1, availabilities: [] },
|
|
});
|
|
});
|
|
|
|
test('merchandises: attaches original gear from the bundled data without its brand', async () => {
|
|
const original = gearData.head[0];
|
|
const brand = {
|
|
id: '0',
|
|
name: 'Brand',
|
|
image: '/images/brand/0.png',
|
|
frequent_skill: { id: '0', name: 'Skill', image: '/images/skill/0.png' },
|
|
};
|
|
const skill = { id: '0', name: 'Skill', image: '/images/skill/0.png' };
|
|
|
|
fakeSplatNet({
|
|
'/api/onlineshop/merchandises': () => ({
|
|
merchandises: [
|
|
{
|
|
end_time: 7200,
|
|
gear: {
|
|
kind: 'head',
|
|
id: '1',
|
|
name: original.name.toUpperCase(),
|
|
image: '/images/gear/1.png',
|
|
brand,
|
|
},
|
|
skill,
|
|
},
|
|
{
|
|
end_time: 7200,
|
|
gear: { kind: 'head', id: '2', name: 'No Such Gear', image: '/images/gear/2.png', brand },
|
|
skill,
|
|
},
|
|
],
|
|
extra: 'dropped',
|
|
}),
|
|
});
|
|
await new MerchandisesUpdater(b).update();
|
|
|
|
const data = await json(b.publicBucket, 'data/merchandises.json');
|
|
|
|
assert.deepEqual(Object.keys(data), ['merchandises']);
|
|
assert.equal(data.merchandises[0].original_gear.name, original.name);
|
|
assert.equal('brand' in data.merchandises[0].original_gear, false);
|
|
assert.ok(data.merchandises[0].original_gear.skill);
|
|
assert.equal(data.merchandises[1].original_gear, null);
|
|
});
|
|
|
|
test('festivals: merges one region into the shared file, fetches missing rankings, uses regional languages', async () => {
|
|
await b.publicBucket.put('data/festivals.json', JSON.stringify({ eu: { festivals: [], results: [] } }));
|
|
await b.publicBucket.put('data/festivals/na-1-rankings.json', JSON.stringify({ existing: true }));
|
|
|
|
const festival = id => ({
|
|
festival_id: id,
|
|
names: { alpha_short: 'A', bravo_short: 'B' },
|
|
times: { start: 3600, end: 7200 },
|
|
images: {
|
|
alpha: '/images/festival/a.png',
|
|
bravo: '/images/festival/b.png',
|
|
panel: '/images/festival/p.png',
|
|
},
|
|
special_stage: { id: '100', name: 'Shifty Station', image: '/images/stage/100.png' },
|
|
});
|
|
const splatnet = fakeSplatNet({
|
|
'/api/festivals/active': () => ({ festivals: [festival(2)] }),
|
|
'/api/festivals/pasts': () => ({
|
|
festivals: [festival(1)],
|
|
results: [{ festival_id: 1 }, { festival_id: 2 }],
|
|
}),
|
|
'/api/festivals/2/rankings': () => ({ rankings: 'two' }),
|
|
});
|
|
|
|
await new FestivalsUpdater('NA', b).update();
|
|
|
|
const data = await json(b.publicBucket, 'data/festivals.json');
|
|
|
|
assert.deepEqual(Object.keys(data).sort(), ['eu', 'na']);
|
|
assert.deepEqual(
|
|
data.na.festivals.map(f => f.festival_id),
|
|
[2, 1],
|
|
);
|
|
assert.deepEqual(await json(b.publicBucket, 'data/festivals/na-1-rankings.json'), { existing: true });
|
|
assert.deepEqual(await json(b.publicBucket, 'data/festivals/na-2-rankings.json'), { rankings: 'two' });
|
|
assert.deepEqual(
|
|
splatnet.api.filter(r => r.path.endsWith('/rankings')).map(r => r.path),
|
|
['/api/festivals/2/rankings'],
|
|
);
|
|
|
|
// Only this region's languages are localized (NA: en, es-MX, fr-CA), all with the NA session
|
|
const localized = splatnet.api.filter(r => r.path === '/api/festivals/active');
|
|
|
|
assert.deepEqual(
|
|
localized.map(r => `${r.region}/${r.language}`),
|
|
['NA/en', 'NA/es-MX', 'NA/fr-CA'],
|
|
);
|
|
|
|
const ics = await (await b.publicBucket.get('data/festivals-na.ics')).text();
|
|
|
|
assert.match(ics, /X-WR-CALNAME:Splatfests \(NA\)/);
|
|
assert.match(ics, /SUMMARY:NA Splatfest: A vs. B/);
|
|
});
|
|
|
|
test('original gear: mirrors skill images from the bundled skills data', async () => {
|
|
const images = Object.values(skills)
|
|
.map(s => s.image)
|
|
.filter(Boolean);
|
|
|
|
await b.publicBucket.put(`assets/splatnet${images[0]}`, 'existing');
|
|
|
|
const splatnet = fakeSplatNet();
|
|
|
|
await new OriginalGearImageUpdater(b).update();
|
|
|
|
assert.equal(splatnet.images.length, images.length - 2); // one already present, one from the embedded backup
|
|
assert.equal(keys(b.publicBucket).length, images.length);
|
|
assert.ok(keys(b.publicBucket).every(k => k.startsWith('assets/splatnet/images/skill/')));
|
|
});
|