fix(app): http:// and https:// on one host are one account on the device

The reading of the full reading's fixes found that the device keeps
http://host/ and https://host/ in one place (folder, session key, keyring
prefix): someone with a stored http://host/ who added https://host/ and
then removed the first deleted the second's folder, session and keys while
it was in use, losing unsynced changes, and an anonymous account for good.

The places keep the names installs made (InstanceStorage.sharesWith says
which addresses share one): choosing an address the device keeps where a
known one is puts it in that one's place in the list, and removing an
address forgets nothing another known one uses. The build's own instance
keeps its place apart. Learn and Todo pass the storage's rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 16:28:14 +02:00
commit 16ad925095
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -14,6 +14,7 @@ void main() {
forget: (instance) async {
forgotten.add(instance);
},
sharesStorage: InstanceStorage('/support', builtIn: builtIn).sharesWith,
);
setUp(() {
@ -148,4 +149,73 @@ void main() {
expect(instances.known, [builtIn]);
expect(forgotten, [insecure]);
});
group('two addresses the device keeps in one place (http:// and https:// '
'on one host; the reading of the fixes)', () {
final insecure = Instance.parse('http://learn.example.org')!;
test('are one account: choosing one puts it in the place of the other, '
'forgetting nothing', () async {
store = MemoryInstanceStore((
current: insecure,
known: [builtIn, insecure],
));
final instances = choice();
await instances.load(usedBefore: false);
await instances.use(other);
expect(instances.current, other);
expect(instances.known, [builtIn, other]);
expect(forgotten, isEmpty);
});
test('removing one never forgets the folder, session or keys the other '
'uses', () async {
// Both known, from before.
store = MemoryInstanceStore((
current: insecure,
known: [builtIn, insecure, other],
));
final instances = choice();
await instances.load(usedBefore: false);
// Refused, and left for good, as the screen for an address over
// plain HTTP offers.
await instances.leave(insecure, forget: true);
expect(instances.known, [builtIn, other]);
expect(forgotten, isEmpty);
store = MemoryInstanceStore((
current: other,
known: [builtIn, insecure, other],
));
final again = choice();
await again.load(usedBefore: false);
await again.remove(insecure);
expect(again.known, [builtIn, other]);
expect(forgotten, isEmpty);
});
});
test('the storage tells which addresses it keeps in one place, the '
"build's own apart", () {
final storage = InstanceStorage('/support', builtIn: builtIn);
final insecure = Instance.parse('http://learn.example.org')!;
final builtInsTwin = Instance.parse('http://learn.halkyon.eu')!;
expect(storage.sharesWith(insecure, other), isTrue);
expect(storage.sharesWith(other, other), isFalse);
expect(storage.sharesWith(builtIn, builtInsTwin), isFalse);
expect(
storage.sharesWith(other, Instance.parse('learn.example.net')!),
isFalse,
);
// Their places as before: none moved.
expect(
storage.peopleOf(other).path,
'/support/instances/learn.example.org/people',
);
expect(storage.peopleOf(builtIn).path, '/support/people');
});
}