From d3c86cf6665655466272ba88eefa5dbdc0f29de1 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:22:07 +0200 Subject: [PATCH] test(scenario): network guard x local bytes Exercise the real hasLocalPlaybackUrl + subsonicNetworkGuard: a track with local psysonic-local:// bytes (any tier) skips the reachability probe even when the server is unreachable, while a non-local track stays gated by reachability. Closes the local-bytes network-skip QA (review residual #1). --- .../networkGuardLocalBytes.scenario.test.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/scenarios/networkGuardLocalBytes.scenario.test.ts diff --git a/src/test/scenarios/networkGuardLocalBytes.scenario.test.ts b/src/test/scenarios/networkGuardLocalBytes.scenario.test.ts new file mode 100644 index 00000000..a212c76b --- /dev/null +++ b/src/test/scenarios/networkGuardLocalBytes.scenario.test.ts @@ -0,0 +1,63 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { useAuthStore } from '@/store/authStore'; +import { useLocalPlaybackStore, type LocalPlaybackTier } from '@/store/localPlaybackStore'; +import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; +import { makeServer } from '@/test/helpers/factories'; +import { resetAllStores } from '@/test/helpers/storeReset'; + +// Scenario: network guard × local bytes (closes review residual #1). Uses the REAL +// hasLocalPlaybackUrl + subsonicNetworkGuard: a track that already resolves to +// local psysonic-local:// bytes must skip the reachability probe (plays offline), +// while a non-local track stays gated by reachability. + +const isActiveServerReachable = vi.hoisted(() => vi.fn(() => true)); +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable })); + +const server = makeServer({ id: 'srv-1', url: 'https://demo.example' }); +const INDEX_KEY = serverIndexKeyForProfile(server); +const TRACK = 't1'; + +function seedLocal(tier: LocalPlaybackTier): void { + useLocalPlaybackStore.getState().upsertEntry({ + serverIndexKey: INDEX_KEY, + trackId: TRACK, + localPath: `/disk/${TRACK}.opus`, + layoutFingerprint: 'fp', + sizeBytes: 1, + suffix: 'opus', + tier, + }); +} + +beforeEach(() => { + resetAllStores(); + useAuthStore.setState({ servers: [server], activeServerId: server.id }); + useLocalPlaybackStore.setState({ entries: {} }); + isActiveServerReachable.mockReturnValue(true); +}); + +describe('network guard × local bytes', () => { + it.each(['library', 'favorite-auto', 'ephemeral'])( + 'local %s track → probe skipped (reachable server)', + (tier) => { + seedLocal(tier); + isActiveServerReachable.mockReturnValue(true); + expect(shouldAttemptSubsonicForServer(server.id, TRACK)).toBe(false); + }, + ); + + it('local track → probe skipped even when the server is unreachable', () => { + seedLocal('library'); + isActiveServerReachable.mockReturnValue(false); + expect(shouldAttemptSubsonicForServer(server.id, TRACK)).toBe(false); + }); + + it.each([ + { reachable: true, expected: true }, + { reachable: false, expected: false }, + ])('non-local track → follows reachability (reachable=$reachable)', ({ reachable, expected }) => { + isActiveServerReachable.mockReturnValue(reachable); + expect(shouldAttemptSubsonicForServer(server.id, TRACK)).toBe(expected); + }); +});