You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Music/MusicTests/DatabaseServiceTests.swift

230 lines
9.5 KiB

import Foundation
import Testing
import GRDB
@testable import Music
struct DatabaseServiceTests {
// Creates an in-memory DatabaseService, inserts a track, and fetches it back.
@Test func insertAndFetchTrack() throws {
let db = try DatabaseService(inMemory: true)
var track = Track.fixture(title: "Test Song", artist: "Test Artist")
try db.insert(&track)
let tracks = try db.fetchTracks(search: "", sortColumn: "title", ascending: true)
#expect(tracks.count == 1)
#expect(tracks[0].title == "Test Song")
}
// Inserts multiple tracks and verifies sorting by different columns.
@Test func fetchTracksSortedByArtist() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Zebra", artist: "Alpha")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Alpha", artist: "Zebra")
try db.insert(&t1)
try db.insert(&t2)
let ascending = try db.fetchTracks(search: "", sortColumn: "artist", ascending: true)
#expect(ascending[0].artist == "Alpha")
#expect(ascending[1].artist == "Zebra")
let descending = try db.fetchTracks(search: "", sortColumn: "artist", ascending: false)
#expect(descending[0].artist == "Zebra")
}
// Searches using FTS5 and verifies only matching tracks are returned.
@Test func fts5Search() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Bohemian Rhapsody", artist: "Queen")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Hotel California", artist: "Eagles")
var t3 = Track.fixture(fileURL: "/c.mp3", title: "Stairway to Heaven", artist: "Led Zeppelin")
try db.insert(&t1)
try db.insert(&t2)
try db.insert(&t3)
let results = try db.fetchTracks(search: "queen", sortColumn: "title", ascending: true)
#expect(results.count == 1)
#expect(results[0].title == "Bohemian Rhapsody")
}
// Searches with a prefix to verify autocomplete-style matching.
@Test func fts5PrefixSearch() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Bohemian Rhapsody", artist: "Queen")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Hotel California", artist: "Eagles")
try db.insert(&t1)
try db.insert(&t2)
let results = try db.fetchTracks(search: "boh", sortColumn: "title", ascending: true)
#expect(results.count == 1)
#expect(results[0].title == "Bohemian Rhapsody")
}
// Inserts a batch of tracks and verifies duplicates are silently ignored.
@Test func batchInsertIgnoresDuplicates() throws {
let db = try DatabaseService(inMemory: true)
let tracks = [
Track.fixture(fileURL: "/a.mp3", title: "Song A"),
Track.fixture(fileURL: "/b.mp3", title: "Song B"),
Track.fixture(fileURL: "/a.mp3", title: "Song A Duplicate"),
]
try db.insertBatch(tracks)
let all = try db.fetchTracks(search: "", sortColumn: "title", ascending: true)
#expect(all.count == 2)
}
// Updates play stats and verifies they persist.
@Test func updatePlayStats() throws {
let db = try DatabaseService(inMemory: true)
var track = Track.fixture()
try db.insert(&track)
let now = Date()
try db.updatePlayStats(trackId: track.id!, playCount: 5, lastPlayedAt: now)
let fetched = try db.fetchTracks(search: "", sortColumn: "title", ascending: true)
#expect(fetched[0].playCount == 5)
#expect(fetched[0].lastPlayedAt != nil)
}
// Validates that an invalid sort column falls back to "title".
@Test func invalidSortColumnFallsBackToTitle() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Zebra")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Alpha")
try db.insert(&t1)
try db.insert(&t2)
let result = try db.fetchTracks(search: "", sortColumn: "DROP TABLE tracks", ascending: true)
#expect(result[0].title == "Alpha")
}
// Creates a playlist and verifies it can be fetched back.
@Test func createAndFetchPlaylist() throws {
let db = try DatabaseService(inMemory: true)
let playlist = try db.createPlaylist(name: "Chill Vibes")
#expect(playlist.id != nil)
#expect(playlist.name == "Chill Vibes")
let all = try db.fetchPlaylists()
#expect(all.count == 1)
#expect(all[0].name == "Chill Vibes")
}
// Renames a playlist and verifies the new name persists.
@Test func renamePlaylist() throws {
let db = try DatabaseService(inMemory: true)
let playlist = try db.createPlaylist(name: "Old Name")
try db.renamePlaylist(id: playlist.id!, name: "New Name")
let all = try db.fetchPlaylists()
#expect(all[0].name == "New Name")
}
// Deletes a playlist and verifies it's gone.
@Test func deletePlaylist() throws {
let db = try DatabaseService(inMemory: true)
let playlist = try db.createPlaylist(name: "To Delete")
try db.deletePlaylist(id: playlist.id!)
let all = try db.fetchPlaylists()
#expect(all.isEmpty)
}
// Adds tracks to a playlist and verifies order is preserved.
@Test func addTracksToPlaylist() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Song A")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Song B")
try db.insert(&t1)
try db.insert(&t2)
let playlist = try db.createPlaylist(name: "My Playlist")
try db.addTrackToPlaylist(trackId: t1.id!, playlistId: playlist.id!)
try db.addTrackToPlaylist(trackId: t2.id!, playlistId: playlist.id!)
let tracks = try db.fetchPlaylistTracks(playlistId: playlist.id!)
#expect(tracks.count == 2)
#expect(tracks[0].title == "Song A")
#expect(tracks[1].title == "Song B")
}
// Removes a track from a playlist and verifies positions are compacted.
@Test func removeTrackFromPlaylist() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Song A")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Song B")
var t3 = Track.fixture(fileURL: "/c.mp3", title: "Song C")
try db.insert(&t1)
try db.insert(&t2)
try db.insert(&t3)
let playlist = try db.createPlaylist(name: "My Playlist")
try db.addTrackToPlaylist(trackId: t1.id!, playlistId: playlist.id!)
try db.addTrackToPlaylist(trackId: t2.id!, playlistId: playlist.id!)
try db.addTrackToPlaylist(trackId: t3.id!, playlistId: playlist.id!)
// Remove middle track
try db.removeTrackFromPlaylist(trackId: t2.id!, playlistId: playlist.id!)
let tracks = try db.fetchPlaylistTracks(playlistId: playlist.id!)
#expect(tracks.count == 2)
#expect(tracks[0].title == "Song A")
#expect(tracks[1].title == "Song C")
}
// Reorders tracks in a playlist (move last to first).
@Test func reorderPlaylistTracks() throws {
let db = try DatabaseService(inMemory: true)
var t1 = Track.fixture(fileURL: "/a.mp3", title: "Song A")
var t2 = Track.fixture(fileURL: "/b.mp3", title: "Song B")
var t3 = Track.fixture(fileURL: "/c.mp3", title: "Song C")
try db.insert(&t1)
try db.insert(&t2)
try db.insert(&t3)
let playlist = try db.createPlaylist(name: "My Playlist")
try db.addTrackToPlaylist(trackId: t1.id!, playlistId: playlist.id!)
try db.addTrackToPlaylist(trackId: t2.id!, playlistId: playlist.id!)
try db.addTrackToPlaylist(trackId: t3.id!, playlistId: playlist.id!)
// Move Song C (position 2) to position 0
try db.reorderPlaylistTrack(playlistId: playlist.id!, fromPosition: 2, toPosition: 0)
let tracks = try db.fetchPlaylistTracks(playlistId: playlist.id!)
#expect(tracks[0].title == "Song C")
#expect(tracks[1].title == "Song A")
#expect(tracks[2].title == "Song B")
}
// Verifies that deleting a playlist cascades to playlist_tracks.
@Test func deletePlaylistCascadesJoinRows() throws {
let db = try DatabaseService(inMemory: true)
var track = Track.fixture()
try db.insert(&track)
let playlist = try db.createPlaylist(name: "Temp")
try db.addTrackToPlaylist(trackId: track.id!, playlistId: playlist.id!)
try db.deletePlaylist(id: playlist.id!)
// Re-create a playlist to verify no orphan join rows cause issues
let playlist2 = try db.createPlaylist(name: "Temp2")
let tracks = try db.fetchPlaylistTracks(playlistId: playlist2.id!)
#expect(tracks.isEmpty)
}
// Verifies adding a duplicate track to a playlist is silently ignored.
@Test func addDuplicateTrackToPlaylistIsIgnored() throws {
let db = try DatabaseService(inMemory: true)
var track = Track.fixture()
try db.insert(&track)
let playlist = try db.createPlaylist(name: "My Playlist")
try db.addTrackToPlaylist(trackId: track.id!, playlistId: playlist.id!)
try db.addTrackToPlaylist(trackId: track.id!, playlistId: playlist.id!)
let tracks = try db.fetchPlaylistTracks(playlistId: playlist.id!)
#expect(tracks.count == 1)
}
}