Keep using Photos.app like you always do. Attic quietly backs up your originals and edits to an S3 bucket you control. One-way, append-only.
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add tests for isNotFoundError with S3ClientError types

Verify that S3ManifestStore.load() returns an empty manifest for
httpError(404) and s3Error("NoSuchKey"), and throws on non-404 errors.
These cover the isNotFoundError fix for the new URLSession S3 client.

+38
+38
Tests/AtticCoreTests/S3ManifestStoreTests.swift
··· 62 62 } 63 63 } 64 64 65 + /// S3 provider that throws a configurable error on getObject. 66 + private actor ThrowingS3Provider: S3Providing { 67 + let error: Error 68 + 69 + init(error: Error) { 70 + self.error = error 71 + } 72 + 73 + func putObject(key: String, body: Data, contentType: String?) async throws {} 74 + func putObject(key: String, fileURL: URL, contentType: String?) async throws {} 75 + func getObject(key: String) async throws -> Data { throw error } 76 + func headObject(key: String) async throws -> S3ObjectMeta? { nil } 77 + func listObjects(prefix: String) async throws -> [S3ListObject] { [] } 78 + } 79 + 65 80 @Suite("S3ManifestStore") 66 81 struct S3ManifestStoreTests { 67 82 @Test func loadReturnsEmptyWhenKeyMissing() async throws { ··· 69 84 let store = S3ManifestStore(s3: s3) 70 85 let manifest = try await store.load() 71 86 #expect(manifest.entries.isEmpty) 87 + } 88 + 89 + @Test func loadReturnsEmptyOnHTTP404() async throws { 90 + let s3 = ThrowingS3Provider(error: S3ClientError.httpError(404, "manifest.json")) 91 + let store = S3ManifestStore(s3: s3) 92 + let manifest = try await store.load() 93 + #expect(manifest.entries.isEmpty) 94 + } 95 + 96 + @Test func loadReturnsEmptyOnS3NoSuchKey() async throws { 97 + let s3 = ThrowingS3Provider( 98 + error: S3ClientError.s3Error(code: "NoSuchKey", message: "Not found")) 99 + let store = S3ManifestStore(s3: s3) 100 + let manifest = try await store.load() 101 + #expect(manifest.entries.isEmpty) 102 + } 103 + 104 + @Test func loadThrowsOnNon404HTTPError() async throws { 105 + let s3 = ThrowingS3Provider(error: S3ClientError.httpError(403, "manifest.json")) 106 + let store = S3ManifestStore(s3: s3) 107 + await #expect(throws: S3ClientError.self) { 108 + _ = try await store.load() 109 + } 72 110 } 73 111 74 112 @Test func saveAndLoadRoundTrip() async throws {