337 lines
27 KiB
JavaScript
337 lines
27 KiB
JavaScript
// Playlists : horodatages / réparation au démarrage, ordre, déplacements, limites (PlaylistManager),
|
|
// playlists partagées par serveur (GuildPlaylists), option du serveur (ServerSettings) et routes d'administration
|
|
// associées (Admin.js). Vrais modules et vraie base JSON, mais TOUS les chemins de __glob qui pointent vers
|
|
// backend/data sont redirigés vers un dossier temporaire. Aucun appel réseau (Finder / Spotify / Google simulés).
|
|
const Module = require('module');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const { BACKEND, SCRATCH, __glob, isolateData, check, done } = require('./_setup');
|
|
|
|
// --- Données isolées : chaque chemin de data/ est redirigé AVANT de charger le moindre module ---
|
|
const DATA_DIR = path.join(BACKEND, 'data');
|
|
const realGuildFile = path.join(DATA_DIR, 'guild_playlists.json');
|
|
const realGuildFileExisted = fs.existsSync(realGuildFile);
|
|
const tmp = fs.mkdtempSync(path.join(SCRATCH, 'guild-playlists-'));
|
|
for (const [key, value] of Object.entries(__glob)) {
|
|
if (typeof value === 'string' && path.resolve(value).toLowerCase().startsWith(DATA_DIR.toLowerCase())) {
|
|
__glob[key] = path.join(tmp, path.relative(DATA_DIR, value));
|
|
}
|
|
}
|
|
__glob.DATA = tmp;
|
|
isolateData('guild-playlists-yt');
|
|
const safePaths = Object.entries(__glob).every(([k, v]) => typeof v !== 'string' || !path.resolve(v).toLowerCase().startsWith(DATA_DIR.toLowerCase()));
|
|
if (!safePaths || !__glob.GUILD_PLAYLISTS_DB || path.dirname(__glob.GUILD_PLAYLISTS_DB) !== tmp) {
|
|
console.error('Chemins non isolés : arrêt');
|
|
process.exit(1);
|
|
}
|
|
|
|
const USER = '111111111111111111';
|
|
const OTHER = '444444444444444444';
|
|
const GUILD = '222222222222222222';
|
|
const GUILD_UNKNOWN = '999999999999999999';
|
|
const LEGACY_ID = '1754933300538';
|
|
|
|
// --- Fichiers synthétiques (anciennes entrées sans dates, identifiant numérique, durée en texte) ---
|
|
const ytSong = (id, extra = {}) => ({ type: 'youtube', url: `https://www.youtube.com/watch?v=${id}`, id, title: 'Titre ' + id, author: 'Artiste', duration: 100, ...extra });
|
|
fs.writeFileSync(__glob.PLAYLISTFILE, JSON.stringify({
|
|
[USER]: [
|
|
{ title: 'Ancienne', playlistId: LEGACY_ID, url: null, songs: [ytSong('a', { duration: '120' })], type: 'playlist' },
|
|
{ title: 'Numérique', playlistId: 1754933300999, url: null, songs: [], type: 'playlist' },
|
|
{ title: 'Sans id', url: 'https://www.youtube.com/playlist?list=PLx', songs: [], type: 'youtube', syncedAt: '2026-01-02T03:04:05.000Z' }
|
|
]
|
|
}));
|
|
fs.writeFileSync(__glob.GUILD_PLAYLISTS_DB, JSON.stringify({
|
|
[GUILD]: [{ title: 'Partagée ancienne', playlistId: 1700000000000, url: null, songs: [ytSong('g', { duration: '30' })], type: 'playlist' }]
|
|
}));
|
|
fs.writeFileSync(__glob.SERVER_DB, JSON.stringify({ [GUILD]: { secureRole: null } }));
|
|
|
|
const SRC = path.join(BACKEND, 'src');
|
|
const norm = (p) => p.replace(/\\/g, '/').toLowerCase().replace(/\.js$/, '');
|
|
|
|
// --- Stubs (réseau, Discord, administration) ---
|
|
let finderCalls = 0;
|
|
const finderStub = {
|
|
search: async (url, multiple, forceType) => {
|
|
finderCalls++;
|
|
if (forceType !== 'PLAYLIST' || url.includes('echec')) return null;
|
|
const { Playlist } = require(path.join(SRC, 'playlists', 'Playlist.js'));
|
|
const songs = [ytSong('r1'), ytSong('r2', { duration: 200 })];
|
|
const playlist = new Playlist('Distante', url, 'Chaîne', 'https://www.youtube.com/channel/UCx', songs, 'https://i.ytimg.com/vi/r1/hq.jpg');
|
|
playlist.type = 'youtube';
|
|
return playlist;
|
|
}
|
|
};
|
|
const admin = { identity: { id: USER, username: 'testadmin', global_name: 'Admin', avatar: null }, labels: ['ADMIN'], isAdmin: () => true, isFullBanned: () => false };
|
|
const guilds = new Map([[GUILD, { id: GUILD, name: 'Serveur Test', icon: null, owner: OTHER, allMembers: [USER, OTHER] }]]);
|
|
const stubs = {
|
|
[norm(SRC + '/player/Finder')]: finderStub,
|
|
[norm(SRC + '/media/SpotifyInformation')]: { getTracks: async (p) => p },
|
|
[norm(SRC + '/playlists/Google/YoutubeList')]: {},
|
|
[norm(SRC + '/playlists/Google/OAuth2')]: { isLinked: () => false },
|
|
[norm(SRC + '/media/MediaInformation')]: {},
|
|
[norm(SRC + '/server/auth/User')]: { getUserById: (id) => id === USER ? admin : null, getUsers: () => [admin] },
|
|
[norm(SRC + '/server/auth/Session')]: { getSessionCount: () => 0 },
|
|
[norm(SRC + '/player/Player')]: { getAllPlayers: () => [], AllPlayers: new Map() },
|
|
[norm(SRC + '/discord/Bot')]: { getGuilds: () => guilds, getClient: () => ({ guilds: { cache: new Map() } }), getChannel: () => null, getGuildRoles: () => [] },
|
|
[norm(SRC + '/discord/MediaBase')]: {},
|
|
[norm(SRC + '/utils/WarpProxy')]: {},
|
|
[norm(SRC + '/utils/LogReader')]: { init() {} },
|
|
[norm(SRC + '/utils/Maintenance')]: {},
|
|
[norm(SRC + '/utils/Database/Configuration')]: { getMediaGuildId: () => '', getMediaChannelId: () => '' },
|
|
[norm(SRC + '/youtube')]: {},
|
|
[norm(SRC + '/player/Method/Youtube')]: { activeProcesses: new Map() },
|
|
};
|
|
const origLoad = Module._load;
|
|
Module._load = function (request, parent) {
|
|
if (request.startsWith('.') && parent) {
|
|
const resolved = norm(path.resolve(path.dirname(parent.filename), request));
|
|
if (stubs[resolved]) return stubs[resolved];
|
|
}
|
|
return origLoad.apply(this, arguments);
|
|
};
|
|
|
|
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
|
|
const PlaylistManager = require(path.join(SRC, 'playlists', 'PlaylistManager.js'));
|
|
const GuildPlaylists = require(path.join(SRC, 'playlists', 'GuildPlaylists.js'));
|
|
const ServerSettings = require(path.join(SRC, 'discord', 'ServerSettings.js'));
|
|
const utils = require(path.join(SRC, 'playlists', 'PlaylistUtils.js'));
|
|
const Admin = require(path.join(SRC, 'server', 'Admin.js'));
|
|
|
|
const guildEvents = [];
|
|
process.on('GUILD_PLAYLISTS_UPDATE', (guildId) => guildEvents.push(guildId));
|
|
|
|
(async () => {
|
|
console.log('# PlaylistManager : réparation au démarrage');
|
|
let disk = readJson(__glob.PLAYLISTFILE)[USER];
|
|
check('date déduite de l\'identifiant', disk[0].createdAt === new Date(Number(LEGACY_ID)).toISOString() && disk[0].updatedAt === disk[0].createdAt, disk[0]);
|
|
check('identifiant numérique converti en chaîne + daté', disk[1].playlistId === '1754933300999' && disk[1].createdAt === new Date(1754933300999).toISOString());
|
|
check('identifiant attribué + date de synchro', typeof disk[2].playlistId === 'string' && disk[2].createdAt === '2026-01-02T03:04:05.000Z');
|
|
check('durée en texte réparée', disk[0].songs[0].duration === 120 && disk[0].duration === 120);
|
|
|
|
console.log('# PlaylistManager : lecture sans écriture');
|
|
const before = fs.readFileSync(__glob.PLAYLISTFILE, 'utf-8');
|
|
const empty = PlaylistManager.getPlaylistsOfUser(OTHER);
|
|
check('utilisateur inconnu : tableau vide', Array.isArray(empty) && empty.length === 0);
|
|
check('aucune clé créée sur lecture', fs.readFileSync(__glob.PLAYLISTFILE, 'utf-8') === before && !(OTHER in readJson(__glob.PLAYLISTFILE)));
|
|
check('playlist inconnue : null', PlaylistManager.getPlaylistOfUser(OTHER, '1') === null);
|
|
|
|
console.log('# PlaylistManager : création');
|
|
const manual = await PlaylistManager.addPlaylist(USER, 'Ma liste', null, 'testadmin', USER, null);
|
|
check('playlist vide créée', manual && manual.title === 'Ma liste' && manual.type === 'playlist' && Array.isArray(manual.songs));
|
|
check('avatar par défaut si aucun avatar', /^https:\/\/cdn\.discordapp\.com\/embed\/avatars\/[0-5]\.png$/.test(manual.authorAvatar), manual.authorAvatar);
|
|
check('authorId = profil Discord', manual.authorId === `https://discord.com/users/${USER}`);
|
|
check('createdAt = updatedAt (ISO)', utils.isValidIso(manual.createdAt) && manual.createdAt === manual.updatedAt);
|
|
const withAvatar = utils.discordAvatarUrl(USER, 'a_0123456789abcdef');
|
|
check('avatar animé en gif', withAvatar === `https://cdn.discordapp.com/avatars/${USER}/a_0123456789abcdef.gif?size=128`);
|
|
const imported = await PlaylistManager.addPlaylist(USER, 'Nom perso', 'https://www.youtube.com/playlist?list=PLabc', 'testadmin', USER, null);
|
|
check('import : nom choisi prioritaire', imported && imported.title === 'Nom perso' && imported.customTitle === true && imported.songs.length === 2 && imported.duration === 300, imported && { title: imported.title, d: imported.duration });
|
|
const importedPlain = await PlaylistManager.addPlaylist(USER, 'https://www.youtube.com/playlist?list=PLdef', 'https://www.youtube.com/playlist?list=PLdef', 'testadmin', USER, null);
|
|
check('import : nom identique au lien ignoré', importedPlain && importedPlain.title === 'Distante' && !importedPlain.customTitle);
|
|
check('import en échec : null', await PlaylistManager.addPlaylist(USER, null, 'https://www.youtube.com/playlist?list=echec', 'testadmin', USER, null) === null);
|
|
check('création sans nom ni lien : null', await PlaylistManager.addPlaylist(USER, null, null, 'testadmin', USER, null) === null);
|
|
|
|
console.log('# PlaylistManager : musiques');
|
|
const pid = manual.playlistId;
|
|
manual.updatedAt = '2020-01-01T00:00:00.000Z';
|
|
let res = PlaylistManager.addSong(USER, pid, ytSong('a'));
|
|
check('ajout + updatedAt', res && res.songs.length === 1 && res.updatedAt !== '2020-01-01T00:00:00.000Z');
|
|
check('ajout en JSON accepté', PlaylistManager.addSong(USER, pid, JSON.stringify(ytSong('b'))) !== null);
|
|
PlaylistManager.addSong(USER, pid, ytSong('a'));
|
|
check('musique invalide refusée', PlaylistManager.addSong(USER, pid, { type: 'evil', url: 'http://127.0.0.1/x', id: 'x', title: 'x' }) === null);
|
|
check('hôte non autorisé refusé', PlaylistManager.addSong(USER, pid, { type: 'youtube', url: 'https://evil.example/watch?v=x', id: 'x', title: 'x' }) === null);
|
|
check('playlist inconnue refusée', PlaylistManager.addSong(USER, 'nope', ytSong('c')) === null);
|
|
// [a, b, a] : l'index exact retire la bonne occurrence
|
|
res = PlaylistManager.removeSong(USER, pid, 'a', 2);
|
|
check('retrait par index exact', res && res.songs.map(s => s.id).join(',') === 'a,b', res?.songs?.map(s => s.id));
|
|
PlaylistManager.addSong(USER, pid, ytSong('c'));
|
|
res = PlaylistManager.removeSong(USER, pid, 'c', 0);
|
|
check('index incohérent : première occurrence', res && res.songs.map(s => s.id).join(',') === 'a,b');
|
|
check('musique absente : null', PlaylistManager.removeSong(USER, pid, 'zzz') === null);
|
|
PlaylistManager.addSong(USER, pid, ytSong('c'));
|
|
res = PlaylistManager.moveSong(USER, pid, 0, 2);
|
|
check('déplacement', res && res.songs.map(s => s.id).join(',') === 'b,c,a');
|
|
check('déplacement hors limites : null', PlaylistManager.moveSong(USER, pid, 0, 3) === null && PlaylistManager.moveSong(USER, pid, -1, 0) === null);
|
|
check('durée recalculée', manual.duration === 300);
|
|
// Limite de 5000 titres
|
|
const big = PlaylistManager.getPlaylistOfUser(USER, pid);
|
|
const saved = big.songs;
|
|
big.songs = Array.from({ length: utils.MAX_SONGS }, (_, i) => ({ ...saved[0], id: 'x' + i }));
|
|
check('limite de 5000 titres', PlaylistManager.addSong(USER, pid, ytSong('d')) === null);
|
|
big.songs = saved;
|
|
|
|
console.log('# PlaylistManager : ordre, renommage, actualisation, copie');
|
|
const ids = PlaylistManager.getPlaylistsOfUser(USER).map(p => p.playlistId);
|
|
check('ordre : refus si pas un tableau', PlaylistManager.reorderPlaylists(USER, 'x') === false);
|
|
check('ordre appliqué', PlaylistManager.reorderPlaylists(USER, [ids[3], 'inconnu', ids[0], ids[3], 42]) === true);
|
|
const order = PlaylistManager.getPlaylistsOfUser(USER).map(p => p.playlistId);
|
|
check('connus d\'abord, sans doublon, oubliés à la suite', order.join() === [ids[3], ids[0], ids[1], ids[2], ...ids.slice(4)].join(), order);
|
|
check('ordre enregistré', readJson(__glob.PLAYLISTFILE)[USER].map(p => p.playlistId).join() === order.join());
|
|
check('ordre pour un utilisateur sans playlist : aucune écriture', PlaylistManager.reorderPlaylists(OTHER, []) === true && !(OTHER in readJson(__glob.PLAYLISTFILE)));
|
|
const renamed = PlaylistManager.renamePlaylist(USER, importedPlain.playlistId, 'Renommée');
|
|
check('renommage d\'un import : titre conservé à l\'actualisation', renamed.customTitle === true);
|
|
const createdAt = importedPlain.createdAt;
|
|
renamed.updatedAt = '2020-01-01T00:00:00.000Z';
|
|
const refreshed = await PlaylistManager.refreshPlaylist(USER, importedPlain.playlistId);
|
|
check('actualisation : titre, id, création conservés', refreshed && refreshed.title === 'Renommée' && refreshed.playlistId === importedPlain.playlistId && refreshed.createdAt === createdAt && refreshed.updatedAt !== '2020-01-01T00:00:00.000Z', refreshed && { t: refreshed.title, c: refreshed.createdAt });
|
|
check('actualisation : même position', PlaylistManager.getPlaylistsOfUser(USER).findIndex(p => p.playlistId === importedPlain.playlistId) === order.indexOf(importedPlain.playlistId));
|
|
const syncedLike = { ...PlaylistManager.getPlaylistOfUser(USER, LEGACY_ID), source: 'google', scope: 'guild', guildId: GUILD, createdBy: { id: OTHER } };
|
|
const copy = PlaylistManager.addPlaylistCopy(OTHER, syncedLike);
|
|
check('copie détachée', copy && !copy.source && !copy.scope && !copy.guildId && !copy.createdBy && copy.createdAt && copy.playlistId);
|
|
check('copie : clé créée à l\'écriture', readJson(__glob.PLAYLISTFILE)[OTHER].length === 1);
|
|
|
|
console.log('# PlaylistManager : limite de playlists personnelles');
|
|
const otherList = PlaylistManager.getPlaylistsOfUser(OTHER);
|
|
const otherLength = otherList.length;
|
|
for (let i = otherLength; i < PlaylistManager.MAX_PLAYLISTS; i++) otherList.push({ playlistId: 'p' + i, title: 'x' + i, songs: [] });
|
|
const callsBefore = finderCalls;
|
|
check('limite : hasRoomForPlaylist faux', PlaylistManager.hasRoomForPlaylist(OTHER) === false && PlaylistManager.hasRoomForPlaylist(USER) === true);
|
|
check('limite : création refusée', await PlaylistManager.addPlaylist(OTHER, 'Trop', null, 'membre', OTHER, null) === null);
|
|
check('limite : import refusé sans appel réseau', await PlaylistManager.addPlaylist(OTHER, null, 'https://www.youtube.com/playlist?list=PLzz', 'membre', OTHER, null) === null && finderCalls === callsBefore);
|
|
check('limite : copie refusée', PlaylistManager.addPlaylistCopy(OTHER, syncedLike) === null && PlaylistManager.copyPlaylist(USER, OTHER, LEGACY_ID) === null);
|
|
otherList.splice(otherLength);
|
|
check('limite levée après suppression', PlaylistManager.hasRoomForPlaylist(OTHER) === true);
|
|
|
|
console.log('# ServerSettings : option des playlists partagées');
|
|
const serversBefore = fs.readFileSync(__glob.SERVER_DB, 'utf-8');
|
|
check('valeurs par défaut (ancienne entrée)', JSON.stringify(ServerSettings.getSharedPlaylists(GUILD)) === JSON.stringify({ enabled: false, editors: 'members' }));
|
|
check('serveur inconnu : défauts', ServerSettings.getSharedPlaylists(GUILD_UNKNOWN).enabled === false && ServerSettings.getSharedPlaylists(undefined).editors === 'members');
|
|
check('lecture sans écriture', fs.readFileSync(__glob.SERVER_DB, 'utf-8') === serversBefore);
|
|
check('identifiant invalide refusé', ServerSettings.setSharedPlaylists('__proto__', { enabled: true }) === null && ServerSettings.setSharedPlaylists('abc', { enabled: true }) === null);
|
|
check('patch vide refusé', ServerSettings.setSharedPlaylists(GUILD, {}) === null);
|
|
check('enabled non booléen refusé', ServerSettings.setSharedPlaylists(GUILD, { enabled: 'oui' }) === null);
|
|
check('editors inconnu refusé', ServerSettings.setSharedPlaylists(GUILD, { editors: 'all' }) === null);
|
|
check('aucune écriture sur refus', fs.readFileSync(__glob.SERVER_DB, 'utf-8') === serversBefore);
|
|
let settings = ServerSettings.setSharedPlaylists(GUILD, { enabled: true });
|
|
check('activation', settings && settings.enabled === true && settings.editors === 'members');
|
|
settings = ServerSettings.setSharedPlaylists(GUILD, { editors: 'mods' });
|
|
check('champ absent conservé', settings.enabled === true && settings.editors === 'mods');
|
|
let serversDisk = readJson(__glob.SERVER_DB)[GUILD];
|
|
check('enregistré, rôle sécurisé conservé', serversDisk.secureRole === null && serversDisk.sharedPlaylists.enabled === true && serversDisk.sharedPlaylists.editors === 'mods');
|
|
check('getSecureRole intact', ServerSettings.getSecureRole(GUILD) === null);
|
|
|
|
console.log('# GuildPlaylists : réparation et lecture');
|
|
const legacyGuild = GuildPlaylists.getPlaylists(GUILD)[0];
|
|
check('réparation : id chaîne, dates, rattachement', legacyGuild.playlistId === '1700000000000' && legacyGuild.createdAt === new Date(1700000000000).toISOString() && legacyGuild.scope === 'guild' && legacyGuild.guildId === GUILD);
|
|
check('réparation : durée', legacyGuild.duration === 30);
|
|
const guildBefore = fs.readFileSync(__glob.GUILD_PLAYLISTS_DB, 'utf-8');
|
|
check('serveur inconnu : vide sans écriture', GuildPlaylists.getPlaylists(GUILD_UNKNOWN).length === 0 && GuildPlaylists.getPlaylist(GUILD_UNKNOWN, '1') === null && GuildPlaylists.countPlaylists(GUILD_UNKNOWN) === 0);
|
|
check('reorder sur serveur inconnu : aucune écriture', GuildPlaylists.reorder(GUILD_UNKNOWN, []) === true);
|
|
check('fichier inchangé après lectures', fs.readFileSync(__glob.GUILD_PLAYLISTS_DB, 'utf-8') === guildBefore);
|
|
|
|
console.log('# GuildPlaylists : création et modifications');
|
|
const actor = { id: OTHER, username: 'membre', global_name: 'Membre', avatar: null, tokens: ['secret'] };
|
|
let g = await GuildPlaylists.createPlaylist(GUILD, { name: 'Soirée' }, actor);
|
|
check('création', g.ok && g.playlist.scope === 'guild' && g.playlist.guildId === GUILD && g.playlist.title === 'Soirée');
|
|
check('createdBy sans jeton', JSON.stringify(g.playlist.createdBy) === JSON.stringify({ id: OTHER, username: 'membre', global_name: 'Membre', avatar: null }) && g.playlist.updatedBy.id === OTHER);
|
|
const gid = g.playlist.playlistId;
|
|
check('identifiant unique dans le serveur', GuildPlaylists.getPlaylists(GUILD).filter(p => p.playlistId === gid).length === 1);
|
|
check('création sans nom ni lien refusée', (await GuildPlaylists.createPlaylist(GUILD, {}, actor)).error === 'INVALID_PARAMS');
|
|
check('serveur invalide refusé', (await GuildPlaylists.createPlaylist('abc', { name: 'x' }, actor)).error === 'INVALID_PARAMS');
|
|
check('import en échec : FAILED', (await GuildPlaylists.createPlaylist(GUILD, { url: 'https://www.youtube.com/playlist?list=echec' }, actor)).error === 'FAILED');
|
|
const gImported = await GuildPlaylists.createPlaylist(GUILD, { url: 'https://www.youtube.com/playlist?list=PLg' }, admin);
|
|
check('import partagé', gImported.ok && gImported.playlist.title === 'Distante' && gImported.playlist.createdBy.id === USER && gImported.playlist.songs.length === 2);
|
|
|
|
g = GuildPlaylists.addSong(GUILD, gid, ytSong('s1'), admin);
|
|
check('ajout + updatedBy', g.ok && g.playlist.songs.length === 1 && g.playlist.updatedBy.id === USER);
|
|
check('musique invalide : INVALID_PARAMS', GuildPlaylists.addSong(GUILD, gid, { type: 'x' }, admin).error === 'INVALID_PARAMS');
|
|
check('playlist inconnue : NOT_FOUND', GuildPlaylists.addSong(GUILD, 'nope', ytSong('s1'), admin).error === 'NOT_FOUND');
|
|
GuildPlaylists.addSong(GUILD, gid, ytSong('s2'), admin);
|
|
GuildPlaylists.addSong(GUILD, gid, ytSong('s1'), admin);
|
|
g = GuildPlaylists.removeSong(GUILD, gid, 's1', 2, actor);
|
|
check('retrait par index exact', g.ok && g.playlist.songs.map(s => s.id).join() === 's1,s2' && g.playlist.updatedBy.id === OTHER);
|
|
check('retrait absent : NOT_FOUND', GuildPlaylists.removeSong(GUILD, gid, 'zz', undefined, actor).error === 'NOT_FOUND');
|
|
g = GuildPlaylists.moveSong(GUILD, gid, 1, 0, actor);
|
|
check('déplacement', g.ok && g.playlist.songs.map(s => s.id).join() === 's2,s1');
|
|
check('déplacement invalide', GuildPlaylists.moveSong(GUILD, gid, 0, 9, actor).error === 'INVALID_PARAMS');
|
|
g = GuildPlaylists.renamePlaylist(GUILD, gid, 'Soirée 2', actor);
|
|
check('renommage', g.ok && g.playlist.title === 'Soirée 2');
|
|
const list = GuildPlaylists.getPlaylists(GUILD).map(p => p.playlistId);
|
|
check('ordre commun', GuildPlaylists.reorder(GUILD, [list[2], list[0]]) && GuildPlaylists.getPlaylists(GUILD).map(p => p.playlistId).join() === [list[2], list[0], list[1]].join());
|
|
check('ordre invalide', GuildPlaylists.reorder(GUILD, 'x') === false);
|
|
|
|
check('actualisation d\'une playlist manuelle refusée', (await GuildPlaylists.refreshPlaylist(GUILD, gid, actor)).error === 'INVALID_PARAMS');
|
|
const importedCreated = gImported.playlist.createdAt;
|
|
g = await GuildPlaylists.refreshPlaylist(GUILD, gImported.playlist.playlistId, actor);
|
|
check('actualisation : auteur et création conservés', g.ok && g.playlist.createdBy.id === USER && g.playlist.updatedBy.id === OTHER && g.playlist.createdAt === importedCreated && g.playlist.scope === 'guild' && g.playlist.guildId === GUILD);
|
|
|
|
const personalSynced = { ...PlaylistManager.getPlaylistOfUser(USER, LEGACY_ID), source: 'google', syncedAt: '2026-01-01T00:00:00.000Z' };
|
|
g = GuildPlaylists.importPlaylist(GUILD, personalSynced, admin);
|
|
check('partage d\'une playlist personnelle', g.ok && !g.playlist.source && !g.playlist.syncedAt && g.playlist.scope === 'guild' && g.playlist.title === 'Ancienne' && g.playlist.playlistId);
|
|
const sharedId = g.playlist.playlistId;
|
|
check('copie indépendante', g.playlist.songs !== personalSynced.songs);
|
|
|
|
// Limite de 200 playlists par serveur
|
|
const live = GuildPlaylists.getPlaylists(GUILD);
|
|
const realLength = live.length;
|
|
for (let i = realLength; i < GuildPlaylists.MAX_PLAYLISTS; i++) live.push({ playlistId: 'fill' + i, title: 'x', songs: [] });
|
|
check('limite de 200 : création', (await GuildPlaylists.createPlaylist(GUILD, { name: 'Trop' }, actor)).error === 'LIMIT');
|
|
check('limite de 200 : partage', GuildPlaylists.importPlaylist(GUILD, personalSynced, actor).error === 'LIMIT');
|
|
live.splice(realLength);
|
|
|
|
const diskGuild = readJson(__glob.GUILD_PLAYLISTS_DB)[GUILD];
|
|
check('enregistré sur disque', diskGuild.length === realLength && diskGuild.every(p => p.scope === 'guild' && p.createdAt && p.updatedAt));
|
|
|
|
console.log('# Désactivation : données conservées');
|
|
ServerSettings.setSharedPlaylists(GUILD, { enabled: false });
|
|
check('désactivé : playlists conservées', GuildPlaylists.countPlaylists(GUILD) === realLength && readJson(__glob.GUILD_PLAYLISTS_DB)[GUILD].length === realLength);
|
|
ServerSettings.setSharedPlaylists(GUILD, { enabled: true });
|
|
|
|
console.log('# Administration');
|
|
const handlers = new Map();
|
|
const socket = { id: 'sock1', data: { userId: USER }, rooms: new Set(), on: (name, fn) => handlers.set(name, fn), emit() {}, leave() {}, join() {} };
|
|
const fakeIo = {
|
|
sockets: { adapter: { rooms: new Map() }, sockets: new Map() },
|
|
to: () => ({ emit() {} }), in: () => ({ socketsLeave() {} }), emit() {}, of: () => ({ adapter: { on() {} } })
|
|
};
|
|
Admin.attach({ io: fakeIo, socket, socketUser: () => admin });
|
|
const call = (name, payload) => new Promise((resolve) => handlers.get(name)(payload, resolve));
|
|
|
|
let r = await call('/ADMIN/DATABASES', {});
|
|
check('base GuildPlaylists listée', r.ok && r.data.some(db => db.name === 'GuildPlaylists' && db.entries === 1), r.data?.map(db => db.name));
|
|
r = await call('/ADMIN/PLAYLISTS/LIST', { userId: USER });
|
|
check('LIST : createdAt / updatedAt', r.ok && r.data[0].playlists.every(p => utils.isValidIso(p.createdAt) && utils.isValidIso(p.updatedAt)));
|
|
r = await call('/ADMIN/PLAYLISTS/GUILDS', {});
|
|
const entry = r.data?.find(e => e.guildId === GUILD);
|
|
check('GUILDS : serveur et réglage', r.ok && entry && entry.guildName === 'Serveur Test' && entry.settings.enabled === true && entry.settings.editors === 'mods', r);
|
|
check('GUILDS : playlists résumées', entry && entry.playlists.length === realLength && entry.playlists.every(p => typeof p.songCount === 'number' && !('songs' in p) && p.createdAt));
|
|
check('GUILDS : createdBy léger', entry && entry.playlists.find(p => p.playlistId === gid)?.createdBy?.id === OTHER && !JSON.stringify(entry).includes('secret'));
|
|
r = await call('/ADMIN/PLAYLISTS/GUILDS', { guildId: 'abc' });
|
|
check('GUILDS : guildId invalide', !r.ok && r.error === 'INVALID_PARAMS');
|
|
r = await call('/ADMIN/PLAYLISTS/GUILDS', { guildId: GUILD_UNKNOWN });
|
|
check('GUILDS : filtre sur serveur sans playlist', r.ok && r.data.length === 1 && r.data[0].playlists.length === 0 && !GuildPlaylists.listGuildIds().includes(GUILD_UNKNOWN));
|
|
|
|
guildEvents.length = 0;
|
|
r = await call('/ADMIN/GUILDS/SET_SHARED_PLAYLISTS', { guildId: GUILD, editors: 'members' });
|
|
check('SET_SHARED_PLAYLISTS', r.ok && r.data.sharedPlaylists.enabled === true && r.data.sharedPlaylists.editors === 'members' && r.data.sharedPlaylists.count === realLength, r);
|
|
check('poussée vers le serveur', guildEvents.includes(GUILD));
|
|
r = await call('/ADMIN/GUILDS/SET_SHARED_PLAYLISTS', { guildId: GUILD, editors: 'tous' });
|
|
check('editors invalide', !r.ok && r.error === 'INVALID_PARAMS');
|
|
r = await call('/ADMIN/GUILDS/SET_SHARED_PLAYLISTS', { guildId: GUILD, enabled: 'oui' });
|
|
check('enabled invalide', !r.ok && r.error === 'INVALID_PARAMS');
|
|
r = await call('/ADMIN/GUILDS/SET_SHARED_PLAYLISTS', { guildId: GUILD });
|
|
check('aucune modification', !r.ok && r.error === 'INVALID_PARAMS');
|
|
r = await call('/ADMIN/GUILDS/SET_SHARED_PLAYLISTS', { guildId: GUILD_UNKNOWN, enabled: true });
|
|
check('serveur inconnu', !r.ok && r.error === 'NOT_FOUND');
|
|
r = await call('/ADMIN/GUILDS/LIST', {});
|
|
check('résumé du serveur : sharedPlaylists', r.ok && JSON.stringify(r.data[0].sharedPlaylists) === JSON.stringify({ enabled: true, editors: 'members', count: realLength }), r.data?.[0]?.sharedPlaylists);
|
|
|
|
guildEvents.length = 0;
|
|
r = await call('/ADMIN/PLAYLISTS/DELETE', { guildId: GUILD, playlistId: 'inconnue' });
|
|
check('DELETE partagée inconnue', !r.ok && r.error === 'NOT_FOUND');
|
|
r = await call('/ADMIN/PLAYLISTS/DELETE', { guildId: GUILD, playlistId: sharedId });
|
|
check('DELETE partagée', r.ok && r.data.deleted === true && !GuildPlaylists.getPlaylist(GUILD, sharedId) && guildEvents.includes(GUILD));
|
|
r = await call('/ADMIN/PLAYLISTS/DELETE', { userId: USER, playlistId: pid });
|
|
check('DELETE personnelle toujours possible', r.ok && !PlaylistManager.getPlaylistOfUser(USER, pid));
|
|
|
|
check('suppression d\'une playlist partagée (module)', GuildPlaylists.deletePlaylist(GUILD, gid) === true && GuildPlaylists.deletePlaylist(GUILD, gid) === false);
|
|
|
|
await sleep(50);
|
|
check('backend/data jamais utilisé', fs.existsSync(realGuildFile) === realGuildFileExisted && !fs.existsSync(realGuildFile + '.tmp'));
|
|
|
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
done();
|
|
setTimeout(() => process.exit(process.exitCode || 0), 50);
|
|
})().catch((e) => { console.error(e); process.exit(1); });
|