bring in database

switch to just using source models (no extra db fields)
start re-implementing sync service
This commit is contained in:
austinried
2025-11-07 11:45:13 +09:00
parent f1c734d432
commit 0e6acbed0f
18 changed files with 6747 additions and 625 deletions

View File

@@ -0,0 +1,79 @@
import 'package:subtracks/database/database.dart';
import 'package:subtracks/services/sync_services.dart';
import 'package:subtracks/sources/subsonic/source.dart';
import 'package:test/test.dart';
import '../util/database.dart';
import '../util/subsonic.dart';
void main() {
late SubtracksDatabase db;
late SubsonicSource source;
late int sourceId;
late int sourceIdOther;
late SyncService sync;
setUp(() async {
db = testDatabase();
source = SubsonicSource(testServerClients()[Servers.navidrome]!);
sourceId = await db
.into(db.sources)
.insert(SourcesCompanion.insert(name: 'navidrome'));
sourceIdOther = await db
.into(db.sources)
.insert(SourcesCompanion.insert(name: 'other'));
sync = SyncService(
db: db,
source: source,
sourceId: sourceId,
);
});
tearDown(() async {
await db.close();
});
test('syncArtists', () async {
await db
.into(db.artists)
.insert(
ArtistsCompanion.insert(
sourceId: sourceId,
id: 'shouldBeDeleted',
name: 'shouldBeDeleted',
),
);
await db
.into(db.artists)
.insert(
ArtistsCompanion.insert(
sourceId: sourceIdOther,
id: 'shouldBeKept',
name: 'shouldBeKept',
),
);
await sync.syncArtists();
expect(
await db.managers.artists
.filter((f) => f.sourceId.equals(sourceId))
.count(),
equals(2),
);
expect(
await db.managers.artists
.filter((f) => f.id.equals('shouldBeDeleted'))
.getSingleOrNull(),
isNull,
);
expect(
await db.managers.artists
.filter((f) => f.id.equals('shouldBeKept'))
.getSingleOrNull(),
isNotNull,
);
});
}

View File

@@ -1,147 +1,109 @@
import 'package:collection/collection.dart';
import 'package:http/http.dart';
import 'package:subtracks/sources/subsonic/client.dart';
import 'package:subtracks/sources/subsonic/source.dart';
import 'package:test/test.dart';
class TestHttpClient extends BaseClient {
@override
Future<StreamedResponse> send(BaseRequest request) => request.send();
}
class Server {
Server({
required this.name,
required this.client,
});
final String name;
final SubsonicClient client;
}
import '../util/subsonic.dart';
void main() {
late SubsonicSource source;
groupByTestServer((client) {
late SubsonicSource source;
final clients = [
Server(
name: 'navidrome',
client: SubsonicClient(
http: TestHttpClient(),
address: Uri.parse('http://localhost:4533/'),
username: 'admin',
password: 'password',
),
),
Server(
name: 'gonic',
client: SubsonicClient(
http: TestHttpClient(),
address: Uri.parse('http://localhost:4747/'),
username: 'admin',
password: 'admin',
),
),
];
for (final Server(:name, :client) in clients) {
group(name, () {
setUp(() async {
source = SubsonicSource(client);
});
test('ping', () async {
await source.ping();
});
test('allAlbums', () async {
final items = await source.allAlbums().toList();
expect(items.length, equals(3));
final kosmo = items.firstWhere((a) => a.name == 'Kosmonaut');
expect(kosmo.id.length, greaterThan(0));
expect(kosmo.artistId?.length, greaterThan(0));
expect(kosmo.albumArtist, equals('Ugress'));
expect(kosmo.created.compareTo(DateTime.now()), lessThan(0));
expect(kosmo.coverArt?.length, greaterThan(0));
expect(kosmo.year, equals(2006));
expect(kosmo.starred, isNull);
expect(kosmo.genre, equals('Electronic'));
final retro = items.firstWhere(
(a) => a.name == 'Retroconnaissance EP',
);
final dunno = items.firstWhere(
(a) => a.name == "I Don't Know What I'm Doing",
);
expect(kosmo.recentRank, equals(0));
expect(kosmo.frequentRank, equals(1));
expect(retro.recentRank, equals(1));
expect(retro.frequentRank, equals(0));
expect(dunno.recentRank, isNull);
expect(dunno.frequentRank, isNull);
});
test('allArtists', () async {
final items = await source.allArtists().toList();
expect(items.length, equals(2));
});
test('allSongs', () async {
final items = await source.allSongs().toList();
expect(items.length, equals(20));
});
test('allPlaylists', () async {
final items = await source.allPlaylists().toList();
expect(items.length, equals(0));
});
test('allPlaylistSongs', () async {
final items = await source.allPlaylistSongs().toList();
expect(items.length, equals(0));
});
test('album-artist relation', () async {
final artists = await source.allArtists().toList();
final albums = await source.allAlbums().toList();
final artistAlbums = artists
.map(
(artist) => [
artist.name,
...albums
.where((album) => album.artistId == artist.id)
.map((album) => album.name)
.sorted(),
],
)
.sorted((a, b) => (a[0]).compareTo(b[0]));
expect(artistAlbums.length, equals(2));
expect(
artistAlbums,
equals([
[
'Brad Sucks',
"I Don't Know What I'm Doing",
],
[
'Ugress',
'Kosmonaut',
'Retroconnaissance EP',
],
]),
);
});
setUp(() async {
source = SubsonicSource(client);
});
}
test('ping', () async {
await source.ping();
});
test('allAlbums', () async {
final items = await source.allAlbums().toList();
expect(items.length, equals(3));
final kosmo = items.firstWhere((a) => a.name == 'Kosmonaut');
expect(kosmo.id.length, greaterThan(0));
expect(kosmo.artistId?.length, greaterThan(0));
expect(kosmo.albumArtist, equals('Ugress'));
expect(kosmo.created.compareTo(DateTime.now()), lessThan(0));
expect(kosmo.coverArt?.length, greaterThan(0));
expect(kosmo.year, equals(2006));
expect(kosmo.starred, isNull);
expect(kosmo.genre, equals('Electronic'));
final retro = items.firstWhere(
(a) => a.name == 'Retroconnaissance EP',
);
final dunno = items.firstWhere(
(a) => a.name == "I Don't Know What I'm Doing",
);
expect(kosmo.recentRank, equals(0));
expect(kosmo.frequentRank, equals(1));
expect(retro.recentRank, equals(1));
expect(retro.frequentRank, equals(0));
expect(dunno.recentRank, isNull);
expect(dunno.frequentRank, isNull);
});
test('allArtists', () async {
final items = await source.allArtists().toList();
expect(items.length, equals(2));
});
test('allSongs', () async {
final items = await source.allSongs().toList();
expect(items.length, equals(20));
});
test('allPlaylists', () async {
final items = await source.allPlaylists().toList();
expect(items.length, equals(0));
});
test('allPlaylistSongs', () async {
final items = await source.allPlaylistSongs().toList();
expect(items.length, equals(0));
});
test('album-artist relation', () async {
final artists = await source.allArtists().toList();
final albums = await source.allAlbums().toList();
final artistAlbums = artists
.map(
(artist) => [
artist.name,
...albums
.where((album) => album.artistId == artist.id)
.map((album) => album.name)
.sorted(),
],
)
.sorted((a, b) => (a[0]).compareTo(b[0]));
expect(artistAlbums.length, equals(2));
expect(
artistAlbums,
equals([
[
'Brad Sucks',
"I Don't Know What I'm Doing",
],
[
'Ugress',
'Kosmonaut',
'Retroconnaissance EP',
],
]),
);
});
});
}

10
test/util/database.dart Normal file
View File

@@ -0,0 +1,10 @@
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:subtracks/database/database.dart';
SubtracksDatabase testDatabase() => SubtracksDatabase(
DatabaseConnection(
NativeDatabase.memory(),
closeStreamsSynchronously: true,
),
);

6
test/util/http.dart Normal file
View File

@@ -0,0 +1,6 @@
import 'package:http/http.dart';
class TestHttpClient extends BaseClient {
@override
Future<StreamedResponse> send(BaseRequest request) => request.send();
}

34
test/util/subsonic.dart Normal file
View File

@@ -0,0 +1,34 @@
import 'package:subtracks/sources/subsonic/client.dart';
import 'package:test/test.dart';
import 'http.dart';
enum Servers {
navidrome,
gonic,
}
Map<Servers, SubsonicClient> testServerClients() => {
Servers.navidrome: SubsonicClient(
http: TestHttpClient(),
address: Uri.parse('http://localhost:4533/'),
username: 'admin',
password: 'password',
),
Servers.gonic: SubsonicClient(
http: TestHttpClient(),
address: Uri.parse('http://localhost:4747/'),
username: 'admin',
password: 'admin',
),
};
void groupByTestServer(void Function(SubsonicClient client) callback) {
final clients = testServerClients();
for (final MapEntry(key: server, value: client) in clients.entries) {
group(server.name, () {
callback(client);
});
}
}