Build and Publish / Build and Publish Docker Image (push) Successful in 9m49s
76 lines
5.1 KiB
JavaScript
76 lines
5.1 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const {readFileSync} = require('node:fs');
|
|
const {test} = require('node:test');
|
|
const vm = require('node:vm');
|
|
const {randomUUID} = require('node:crypto');
|
|
const template = readFileSync(`${__dirname}/../templates/player/scripts.html`, 'utf8');
|
|
function downloads(fetchImpl) {
|
|
let code = template.slice(template.indexOf(" Alpine.store('torrents', {"), template.indexOf(" Alpine.store('playlists', {"));
|
|
code = code.replace(/\{\{[^}]+\}\}/g,'true').replace(/sourceTab: .*?,\n/,"sourceTab: 'files',\n");
|
|
const stores={playlists:{list:[{id:10,title:'Mine',kind:'user',is_own:true},{id:20,title:'Other',kind:'user',is_own:false}],reload:async()=>{}}};
|
|
const requests=[];
|
|
class XHR {
|
|
headers={}; upload={}; status=200; responseText='{}';
|
|
open(method,url) {this.url=url;}
|
|
setRequestHeader(key,value) {this.headers[key]=value;}
|
|
send(file) {requests.push({file,headers:this.headers});this.onload();}
|
|
}
|
|
const context=vm.createContext({Alpine:{store:(name,value)=>value?(stores[name]=value):stores[name]},fetch:fetchImpl,
|
|
T:new Proxy({}, {get:(_,key)=>key}),crypto:{randomUUID},XMLHttpRequest:XHR,redirectOnUnauthorized:()=>{},console,setInterval:()=>0,clearInterval:()=>{}});
|
|
vm.runInContext(code,context);
|
|
const store=stores.torrents;
|
|
store._poll=()=>{};store.loadSessions=async()=>{};store.loadAgentStatus=async()=>{};store.loadLocalUploadHistory=async()=>{};
|
|
return {store,requests};
|
|
}
|
|
test('destinations are independent, owned playlists only, new titles required',()=>{
|
|
const {store:s}=downloads();
|
|
assert.deepEqual(Array.from(s.youtubeOwnedPlaylists(),p=>p.id),[10]);
|
|
s.localPlaylistChoice='__new__';assert.equal(s.importDestinationValid('local'),false);
|
|
assert.equal(s.importDestinationValid('torrent'),true);
|
|
s.localFiles=[{name:'song.flac',webkitRelativePath:'Soundtrack/song.flac'}];s.importPlaylistChoiceChanged('local');
|
|
assert.equal(s.localNewPlaylistTitle,'Soundtrack');assert.equal(s.importDestinationValid('local'),true);
|
|
});
|
|
test('failed torrent starts reuse the newly created playlist and send only selected files',async()=>{
|
|
const calls=[];
|
|
const {store:s}=downloads(async(url,opts)=>{
|
|
calls.push({url,data:JSON.parse(opts.body)});
|
|
return url==='/api/player/playlists'?{ok:true,json:async()=>({id:33,title:'Soundtrack',kind:'user',is_own:true})}:{ok:false,json:async()=>({error:'retry'})};
|
|
});
|
|
s.previewData={id:'torrent-1'};s.selected=new Set([1,3]);s.torrentPlaylistChoice='__new__';s.torrentNewPlaylistTitle='Soundtrack';
|
|
await s.start();await s.start();
|
|
assert.equal(calls.filter(c=>c.url==='/api/player/playlists').length,1);
|
|
assert.deepEqual(calls[1].data,{selected_files:[1,3],target_playlist_id:33});
|
|
assert.deepEqual(calls[2].data,calls[1].data);assert.equal(s.loading,false);
|
|
});
|
|
test('local files carry the same playlist destination and retries retain upload IDs',async()=>{
|
|
const {store:s,requests}=downloads();const f={name:'a.flac',size:10};
|
|
s.setLocalFiles([f]);s.localTargetPlaylistId=10;
|
|
await s.uploadLocalFile(f,0,10);await s.uploadLocalFile(f,0,10);
|
|
assert.equal(requests[0].headers['X-Furumusic-Target-Playlist-Id'],'10');
|
|
assert.equal(requests[0].headers['X-Furumusic-Upload-Id'],requests[1].headers['X-Furumusic-Upload-Id']);
|
|
s.clearLocalFiles();s.setLocalFiles([f]);await s.uploadLocalFile(f,0,10);
|
|
assert.equal(requests[2].headers['X-Furumusic-Target-Playlist-Id'],undefined);
|
|
});
|
|
test('partially failed batches keep their destination, create one playlist and retry remaining files',async()=>{
|
|
let creates=0;let attempts=0;
|
|
const {store:s}=downloads(async(url)=>{
|
|
if(url==='/api/player/playlists') {creates++;return {ok:true,json:async()=>({id:44})};}
|
|
return {ok:true};
|
|
});
|
|
s.setLocalFiles([{name:'a.mp3',size:1},{name:'b.mp3',size:1}]);s.localPlaylistChoice='__new__';s.localNewPlaylistTitle='OST';
|
|
s.uploadLocalFile=async()=>{attempts++;if(attempts===2)throw Error('offline');return {};};
|
|
await s.uploadLocalFiles();assert.equal(s.localFiles.length,1);assert.equal(s.localPlaylistLocked,true);
|
|
assert.equal(s.localTargetPlaylistId,44);await s.uploadLocalFiles();
|
|
assert.equal(creates,1);assert.equal(s.localFiles.length,0);assert.equal(s.localFilesUploading,false);
|
|
});
|
|
test('playlist creation failure leaves the batch editable and does not signal upload completion',async()=>{
|
|
const calls=[];const {store:s}=downloads(async(url)=>{calls.push(url);return {ok:false,json:async()=>({error:'failed'})};});
|
|
s.setLocalFiles([{name:'a.mp3',size:1}]);s.localPlaylistChoice='__new__';s.localNewPlaylistTitle='OST';
|
|
await s.uploadLocalFiles();assert.equal(s.localPlaylistLocked,false);assert.equal(s.localFilesUploading,false);
|
|
assert.deepEqual(calls,['/api/player/playlists']);assert.equal(s.localFiles.length,1);
|
|
});
|
|
test('torrent destination locks after start',()=>{
|
|
const {store:s}=downloads();s.currentJob={status:'preview'};assert.equal(s.torrentDestinationLocked(),false);
|
|
s.currentJob.status='paused';assert.equal(s.torrentDestinationLocked(),true);
|
|
});
|