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") } }