fix(profiles): notice a corrected connection creation time

ActiveProfileProvider diffed connections on toConfigJson alone, but
createdAt is a real column and now decides which connection lends a
profile its picture. A creation-time correction was therefore invisible
to the guard and left a stale avatar until the next launch.

Compare createdAt alongside the config. ConnectionRegistry pins
creation order across re-authentication, so this adds no notifications
in normal operation — it only stops an out-of-band correction, such as
a restore or a backfill, from being swallowed.
This commit is contained in:
edde746
2026-08-04 02:22:44 +02:00
parent 7a19f4149e
commit 8624c37041
2 changed files with 50 additions and 2 deletions
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:drift/drift.dart' show Value;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/connection/connection.dart';
@@ -305,6 +306,45 @@ void main() {
);
});
test('a corrected connection creation time re-picks the avatar without a restart', () async {
// createdAt is a real column, not part of toConfigJson, so the
// connection diff guard has to compare it explicitly. Nothing in
// normal operation rewrites it — ConnectionRegistry pins creation
// order across re-auth — but a restore or a backfill can, and
// swallowing that would leave a stale avatar until the next launch.
final profile = Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
await registry.upsert(profile);
await connections.upsert(_jellyfin('a', createdAt: DateTime(2025, 1, 1), primaryImageTag: 'a-tag'));
await connections.upsert(_jellyfin('b', createdAt: DateTime(2026, 1, 1), primaryImageTag: 'b-tag'));
await profileConnections.upsert(
const ProfileConnection(profileId: 'p1', connectionId: 'a', userIdentifier: 'user-a'),
);
await profileConnections.upsert(
const ProfileConnection(profileId: 'p1', connectionId: 'b', userIdentifier: 'user-b'),
);
await provider.initialize();
expect(provider.avatarUrlFor(profile.id), contains('tag=a-tag'));
final changed = Completer<void>();
void listener() {
if ((provider.avatarUrlFor(profile.id)?.contains('b-tag') ?? false) && !changed.isCompleted) {
changed.complete();
}
}
provider.addListener(listener);
addTearDown(() => provider.removeListener(listener));
// Straight to the table: the registry deliberately refuses to restamp
// an existing row, so this stands in for an out-of-band correction.
await (db.update(db.connections)..where((t) => t.id.equals('a'))).write(
ConnectionsCompanion(createdAt: Value(DateTime(2027, 1, 1).millisecondsSinceEpoch)),
);
await changed.future.timeout(const Duration(seconds: 2));
expect(provider.avatarUrlFor(profile.id), contains('tag=b-tag'));
});
test('token and timestamp churn on a link does not notify listeners', () async {
await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
await connections.upsert(_jellyfin('jellyfin', createdAt: DateTime(2025, 1, 1), primaryImageTag: 'avatar-tag'));