Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
Something went wrong. Try again.
6.7 kB · 191 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192import { loggerTest, loggerDebug } from "@foxxmd/logging";import chai, { assert, expect } from 'chai';import asPromised from 'chai-as-promised';import { after, before, describe, it } from 'mocha';import dayjs from "dayjs";import withLocalTmpDir from 'with-local-tmp-dir';import path from 'path';import { initFileCache, initMemoryCache, initValkeyCache, MSCache } from "../../common/Cache.js";import { generatePlay, generatePlayerStateData, generatePlays, normalizePlays } from "../../../core/PlayTestUtils.js";import { ListenProgressPositional, ListenProgressTS } from "../../sources/PlayerState/ListenProgress.js";import { isPortReachableConnect } from "../../utils/NetworkUtils.js";import { getRoot } from "../../ioc.js";import { transientCache } from "../utils/CacheTestUtils.js";import { TestScrobbler } from "../scrobbler/TestScrobbler.js";import { sleep } from "../../utils.js";import {promises} from 'node:fs';
chai.use(asPromised);
describe('#Caching', function () {
describe('#MemoryCaching', function () {
it('Memory cache preserves dayjs', async function () {
const cache = initMemoryCache();
const now = dayjs();
await cache.set('foo', now);
const time = await cache.get('foo');
expect(time).to.not.be.undefined; expect(dayjs.isDayjs(time)).is.true; expect(now.toJSON()).eq((time as any).toJSON()); });
it('Memory cache preserves ListenProgress', async function () {
const cache = initMemoryCache();
const prog = new ListenProgressPositional({ timestamp: dayjs(), position: 35, positionPercent: 50 });
await cache.set('foo', prog);
const cachedProg = await cache.get('foo') as ListenProgressTS;
expect(cachedProg).to.not.be.undefined; expect(cachedProg instanceof ListenProgressTS).is.true; expect(cachedProg.timestamp.toJSON()).eq(prog.timestamp.toJSON()); });
});
describe('#FileCaching', function () {
it('File cache serializes and deserializes dayjs', async function () {
withLocalTmpDir(async () => {
const [keyv, flat] = await initFileCache({ cacheDir: process.cwd() });
const now = dayjs();
await keyv.set('foo', now); flat.save();
const [cleanKeyv, cleanFlat] = await initFileCache({ cacheDir: process.cwd() });
const time = await cleanKeyv.get('foo');
expect(time).to.not.be.undefined; expect(time instanceof dayjs).is.true; expect(now.toJSON()).eq((time as any).toJSON()); flat.destroy();
}, { unsafeCleanup: true }); });
it('File cache serializes and deserializes ListenProgress', async function () {
withLocalTmpDir(async () => {
const [keyv, flat] = await initFileCache({ cacheDir: process.cwd() });
const prog = new ListenProgressPositional({ timestamp: dayjs(), position: 35, positionPercent: 50 });
await keyv.set('foo', prog); await flat.save();
const [cleanKeyv, cleanFlat] = await initFileCache({ cacheDir: process.cwd() });
const cachedProg = await cleanKeyv.get('foo');
expect(cachedProg).to.not.be.undefined; expect(cachedProg instanceof ListenProgressTS).is.true; expect(cachedProg.timestamp.toJSON()).eq(prog.timestamp.toJSON()); flat.destroy();
}, { unsafeCleanup: true }); }); });
describe('#ValkeyCaching', function () { before(async function () { try { await isPortReachableConnect(6379, { host: 'valkey' }); } catch (e) { // don't run valkey tests if valkey isn't present this.skip(); } });
it('Valkey cache serializes and deserializes dayjs', async function () {
const keyv = await initValkeyCache('test', 'redis://valkey:6379'); await keyv.clear();
const now = dayjs();
await keyv.set('foo', now);
const time = await keyv.get('foo');
expect(time).to.not.be.undefined; expect(time instanceof dayjs).is.true; expect(now.toJSON()).eq((time as any).toJSON());
});
it('Valkey cache serializes and deserializes ListenProgress', async function () {
const keyv = await initValkeyCache('test', 'redis://valkey:6379'); await keyv.clear();
const prog = new ListenProgressPositional({ timestamp: dayjs(), position: 35, positionPercent: 50 });
await keyv.set('foo', prog);
const cachedProg = await keyv.get('foo');
expect(cachedProg).to.not.be.undefined; expect(cachedProg instanceof ListenProgressTS).is.true; expect(cachedProg.timestamp.toJSON()).eq(prog.timestamp.toJSON());
}); });
describe('#ScrobbleCache', function () {
afterEach(function () { const root = getRoot(); root.upsert({ cache: () => transientCache }); root.items.cache().init(); });
it('Preserves scrobbles', async function () {
this.timeout(10000);
// why does this take so long? await withLocalTmpDir(async () => {
const root = getRoot(); root.upsert({ cache: () => () => new MSCache(loggerTest, { scrobble: { provider: 'file', connection: process.cwd(), persistInterval: 100 } }) });
const test = new TestScrobbler(); await test.initialize(); const plays = generatePlays(100); await test.queueScrobble(plays, 'testSource'); const queued = test.queuedScrobbles.map(x => x.play); await sleep(101); const dirContents = await promises.readdir('.'); const hasCache = dirContents.some(x => x === 'ms-scrobble.cache'); expect(hasCache).is.true; test.scheduler.stop();
const newTest = new TestScrobbler(); await newTest.initialize(); expect(newTest.queuedScrobbles.length).to.eq(plays.length); expect(newTest.queuedScrobbles[0].play.data.track).to.eq(queued[0].data.track); newTest.scheduler.stop();
}, { unsafeCleanup: true });
});
});
});