Record an album or tag write immich refused
immich answers a bulk album or tag write with HTTP 200 and one result per asset, so a key without album permission produced "filing 0 asset(s)", no recorded failure and exit 0. Refusals other than duplicate now come back from the client and are recorded against the photo and its wiki pages. The album and tag stages also get separate error handling, so a failed album write no longer skips tagging for the run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+91
-13
No files matched your search
@@ -1119,13 +1119,13 @@ class ImmichClient(TestCase):
|
||||
self.assertEqual(im.albums("u1"), {"RGL": "al1"})
|
||||
self.assertEqual(im.createalbum("X"), "al2")
|
||||
self.assertEqual(im.albumassets("al1"), {"a1", "a2"})
|
||||
self.assertEqual(im.addtoalbum("al1", ["a3", "a1"]), 1)
|
||||
self.assertEqual(im.addtoalbum("al1", ["a3", "a1"]), (1, []))
|
||||
|
||||
def test_tags(self):
|
||||
im = self.client(FakeResponse([{"value": "rgl", "id": "t1"}, {"value": "rgl/licence/x", "id": "t2"}]),
|
||||
FakeResponse([{"success": True}, {"success": False, "error": "duplicate"}]))
|
||||
self.assertEqual(im.tagids(["rgl", "rgl/licence/x"]), {"rgl": "t1", "rgl/licence/x": "t2"})
|
||||
self.assertEqual(im.tagassets("t1", ["a1", "a2"]), (1, 1))
|
||||
self.assertEqual(im.tagassets("t1", ["a1", "a2"]), (1, 1, []))
|
||||
|
||||
def test_tagids_skips_malformed_entries(self):
|
||||
im = self.client(FakeResponse([{"value": "rgl", "id": "t1"}, {"value": "rgl/broken"}]))
|
||||
@@ -1136,10 +1136,30 @@ class ImmichClient(TestCase):
|
||||
im = self.client(FakeResponse([{"success": True}] * 500),
|
||||
FakeResponse([{"success": True}] * 500),
|
||||
FakeResponse([{"success": True}] * 200))
|
||||
self.assertEqual(im.addtoalbum("al", ids), 1200)
|
||||
self.assertEqual(im.addtoalbum("al", ids), (1200, []))
|
||||
self.assertEqual(len(rb.OPEN.requests), 3)
|
||||
|
||||
|
||||
class ImmichRefusals(TestCase):
|
||||
def client(self, *responses):
|
||||
rb.OPEN = FakeOpen(*responses)
|
||||
return rb.Immich("https://immich.example/api", "KEY")
|
||||
|
||||
def test_album_refusals_are_returned_not_dropped(self):
|
||||
im = self.client(FakeResponse([{"id": "a1", "success": False, "error": "no_permission"},
|
||||
{"id": "a2", "success": False, "error": "duplicate"},
|
||||
{"id": "a3", "success": True}]))
|
||||
self.assertEqual(im.addtoalbum("al1", ["a1", "a2", "a3"]),
|
||||
(1, [("a1", "no_permission")]))
|
||||
|
||||
def test_tag_refusals_are_returned_not_dropped(self):
|
||||
im = self.client(FakeResponse([{"id": "a1", "success": False, "error": "not_found"},
|
||||
{"id": "a2", "success": False, "error": "duplicate"},
|
||||
{"id": "a3", "success": True}]))
|
||||
self.assertEqual(im.tagassets("t1", ["a1", "a2", "a3"]),
|
||||
(1, 1, [("a1", "not_found")]))
|
||||
|
||||
|
||||
class Hashing(TestCase):
|
||||
def test_checksum_is_base64_sha1(self):
|
||||
path = self.write("f", b"hello")
|
||||
@@ -1401,12 +1421,43 @@ class SyncPhase(TestCase):
|
||||
FakeResponse({"assets": {"items": [], "nextPage": None}}),
|
||||
FakeResponse({"id": "a1", "status": "created"}, status=201), # upload 111
|
||||
http_error(500), # albums
|
||||
FakeResponse([{"value": "rgl", "id": "t1"},
|
||||
{"value": "rgl/licence/cc-by-sa-2.0", "id": "t2"},
|
||||
{"value": "rgl/photographer/P", "id": "t3"}]), # tags still run
|
||||
FakeResponse([{"success": True}]), FakeResponse([{"success": True}]),
|
||||
FakeResponse([{"success": True}]),
|
||||
FakeResponse({"id": "a1", "exifInfo": {}}), # re-read a1
|
||||
FakeResponse(b"")) # update a1
|
||||
failures = rb.syncphase(self.conf(limit=1), rb.Immich("https://i/api", "k"))
|
||||
self.assertEqual(len(failures), 1)
|
||||
reason = failures.items[0][2]
|
||||
key, names, reason = failures.items[0]
|
||||
self.assertEqual(key, "album")
|
||||
self.assertTrue("albums" in reason or "HTTP 500" in reason)
|
||||
self.assertEqual(rb.OPEN.requests[4].full_url, "https://i/api/tags")
|
||||
|
||||
def test_album_refusal_is_recorded_against_the_photo(self):
|
||||
rb.OPEN = FakeOpen(
|
||||
FakeResponse({"id": "u1"}),
|
||||
FakeResponse({"assets": {"items": [], "nextPage": None}}),
|
||||
FakeResponse({"id": "a1", "status": "created"}, status=201), # upload 111
|
||||
FakeResponse([{"albumName": "RGL", "id": "al1", "ownerId": "u1"}]),
|
||||
FakeResponse({"assets": []}),
|
||||
FakeResponse([{"id": "a1", "success": False, "error": "no_permission"}]),
|
||||
FakeResponse([{"value": "rgl", "id": "t1"},
|
||||
{"value": "rgl/licence/cc-by-sa-2.0", "id": "t2"},
|
||||
{"value": "rgl/photographer/P", "id": "t3"}]),
|
||||
FakeResponse([{"id": "a1", "success": False, "error": "no_permission"}]),
|
||||
FakeResponse([{"success": True}]), FakeResponse([{"success": True}]),
|
||||
FakeResponse({"id": "a1", "exifInfo": {}}),
|
||||
FakeResponse(b""))
|
||||
failures = rb.syncphase(self.conf(limit=1), rb.Immich("https://i/api", "k"))
|
||||
self.assertEqual(len(failures), 2)
|
||||
(k1, names1, r1), (k2, names2, r2) = failures.items
|
||||
self.assertEqual((k1, names1), ("111", ["A, E1"]))
|
||||
self.assertIn("album RGL: no_permission", r1)
|
||||
self.assertEqual((k2, names2), ("111", ["A, E1"]))
|
||||
self.assertIn("tag rgl: no_permission", r2)
|
||||
self.assertIn("no_permission", self.stderr.getvalue())
|
||||
|
||||
|
||||
class Run(TestCase):
|
||||
|
||||
Reference in new issue
Block a user