fix(connections): keep a connection's creation time across re-authentication

ConnectionRegistry.upsert already preserved isDefault on conflict but
rewrote created_at from the in-memory model. Re-signing in rebuilds the
connection with DateTime.now() under the same stable id, so the row's
creation time jumped forward on every reauth.

That was cosmetic while created_at only drove list ordering. It is now
behaviour: it picks which connection lends a profile its picture, so
re-adding the originally-first connection could hand the avatar to a
later one. remove() also promotes the oldest remaining row to default
and was reading the same restamped value.

Preserve the existing row's created_at on conflict, reusing the lookup
upsert already performs for isDefault.
This commit is contained in:
edde746
2026-08-04 02:22:44 +02:00
parent 860ce1e11a
commit 7a19f4149e
2 changed files with 36 additions and 5 deletions
+24 -2
View File
@@ -20,7 +20,7 @@ Future<String?> _defaultConnectionId(AppDatabase db) async {
return null;
}
JellyfinConnection _jellyfin({String id = 'srv-1', String userName = 'edde'}) {
JellyfinConnection _jellyfin({String id = 'srv-1', String userName = 'edde', int createdAtMs = 1_000_000}) {
return JellyfinConnection(
id: id,
baseUrl: 'https://jellyfin.local',
@@ -30,7 +30,7 @@ JellyfinConnection _jellyfin({String id = 'srv-1', String userName = 'edde'}) {
userName: userName,
accessToken: 'tok-$id',
deviceId: 'dev-1',
createdAt: DateTime.fromMillisecondsSinceEpoch(1_000_000),
createdAt: DateTime.fromMillisecondsSinceEpoch(createdAtMs),
);
}
@@ -184,6 +184,28 @@ void main() {
expect(await _defaultConnectionId(db), 'a');
});
test('re-upsert preserves the original creation order', () async {
// Regression: creation order decides which connection lends a profile
// its picture (issue #1667) and which row `remove` promotes to default.
// Re-authenticating rebuilds the model with `DateTime.now()` under the
// same stable id, so an unguarded writer restamped the originally-first
// connection and shuffled it to last.
await registry.upsert(_jellyfin(id: 'first', createdAtMs: 1_000_000));
await registry.upsert(_jellyfin(id: 'second', createdAtMs: 2_000_000));
await registry.upsert(_jellyfin(id: 'first', createdAtMs: 9_000_000));
final list = await registry.list();
expect(list.map((c) => c.id).toList(), ['first', 'second']);
expect(list.first.createdAt, DateTime.fromMillisecondsSinceEpoch(1_000_000));
});
test('a genuinely new connection keeps the creation time it was built with', () async {
await registry.upsert(_jellyfin(id: 'a', createdAtMs: 5_000_000));
expect((await registry.list()).single.createdAt, DateTime.fromMillisecondsSinceEpoch(5_000_000));
});
test('recordAuthSuccess updates lastAuthenticatedAt without losing config', () async {
await registry.upsert(_jellyfin(id: 'a'));
final at = DateTime.fromMillisecondsSinceEpoch(2_000_000);