File size: 4,788 Bytes
00a912e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import assert from 'node:assert/strict'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import test, { after } from 'node:test'

const testDataDir = mkdtempSync(path.join(tmpdir(), 'music-together-offline-members-'))
process.env.DATABASE_URL = `file:${path.join(testDataDir, 'test.db')}`

const roomService = await import('../src/services/roomService.js')
const { db } = await import('../src/repositories/database.js')
const { InMemoryRoomRepository, roomRepo } = await import('../src/repositories/roomRepository.js')

after(() => {
  db.close()
  rmSync(testDataDir, { recursive: true, force: true })
})

test('keeps offline members in a permanent room roster after reload', () => {
  const { room } = roomService.createRoom('owner-socket', 'Owner', 'Roster room', null, 'owner-id')
  roomService.updateSettings(room.id, { permanent: true })

  const joined = roomService.joinRoom('member-socket', room.id, 'Member', 'member-id')
  assert.ok(joined)
  assert.equal(joined.room.members.length, 2)
  assert.equal(joined.room.members.find((member) => member.id === 'member-id')?.isOnline, true)

  roomService.leaveRoom('member-socket')
  const offlineMember = room.members.find((member) => member.id === 'member-id')
  assert.deepEqual(
    offlineMember && {
      id: offlineMember.id,
      role: offlineMember.role,
      isOnline: offlineMember.isOnline,
      hasLastSeenAt: offlineMember.lastSeenAt !== null,
    },
    { id: 'member-id', role: 'member', isOnline: false, hasLastSeenAt: true },
  )

  const persistedCount = db
    .prepare<[string], { count: number }>('SELECT COUNT(*) AS count FROM permanent_room_members WHERE room_id = ?')
    .get(room.id)
  assert.equal(persistedCount?.count, 2)

  const restoredRepo = new InMemoryRoomRepository()
  const restoredRoom = restoredRepo.get(room.id)
  assert.equal(restoredRoom?.users.length, 0)
  assert.deepEqual(
    restoredRoom?.members.map((member) => ({ id: member.id, isOnline: member.isOnline })),
    [
      { id: 'owner-id', isOnline: false },
      { id: 'member-id', isOnline: false },
    ],
  )

  roomRepo.delete(room.id)
})

test('restores the permanent room track and saved playback position after reload', () => {
  const { room } = roomService.createRoom('player-socket', 'Player', 'Playback room', null, 'player-id')
  roomService.updateSettings(room.id, { permanent: true })
  room.currentTrack = {
    id: 'saved-track',
    title: 'Saved song',
    artist: ['Saved artist'],
    album: 'Saved album',
    duration: 240,
    cover: '',
    source: 'netease',
    sourceId: '123',
    urlId: '123',
    streamUrl: 'https://expired.example.test/stream',
  }
  room.playState = { isPlaying: true, currentTime: 91.25, serverTimestamp: Date.now() - 1_000 }
  roomRepo.persist(room.id)

  const restoredRoom = new InMemoryRoomRepository().get(room.id)
  const { streamUrl: _streamUrl, ...expectedTrack } = room.currentTrack
  assert.deepEqual(restoredRoom?.currentTrack, expectedTrack)
  assert.deepEqual(restoredRoom?.playState.isPlaying, false)
  assert.equal(restoredRoom?.playState.currentTime, 91.25)

  roomRepo.delete(room.id)
})

test('updates the roster role when an online member becomes temporary admin', () => {
  const { room } = roomService.createRoom('owner-socket', 'Owner', 'Role sync room', null, 'owner-role-id')
  const joined = roomService.joinRoom('member-socket', room.id, 'Member', 'member-role-id')
  assert.ok(joined)

  const left = roomService.leaveRoom('owner-socket')
  assert.ok(left)
  assert.equal(left.roleChanged, true)
  assert.equal(room.users.find((user) => user.id === 'member-role-id')?.role, 'admin')
  assert.equal(room.members.find((member) => member.id === 'member-role-id')?.role, 'admin')

  roomRepo.delete(room.id)
})

test('clears a temporary admin role when the room becomes empty before owner returns', () => {
  const { room } = roomService.createRoom('owner-empty-socket', 'Owner', 'Role reset room', null, 'owner-empty-id')
  const ownerLeft = roomService.leaveRoom('owner-empty-socket')
  assert.ok(ownerLeft)

  const memberJoined = roomService.joinRoom('member-empty-socket', room.id, 'Member', 'member-empty-id')
  assert.ok(memberJoined)
  assert.equal(room.members.find((member) => member.id === 'member-empty-id')?.role, 'admin')

  const memberLeft = roomService.leaveRoom('member-empty-socket')
  assert.ok(memberLeft)
  assert.equal(room.members.find((member) => member.id === 'member-empty-id')?.role, 'member')

  const ownerReturned = roomService.joinRoom('owner-return-socket', room.id, 'Owner', 'owner-empty-id')
  assert.ok(ownerReturned)
  assert.equal(ownerReturned.room.members.find((member) => member.id === 'member-empty-id')?.role, 'member')

  roomRepo.delete(room.id)
})