Date photo uploads from capture metadata

This commit is contained in:
2026-09-20 01:33:04 +03:30
parent afb4ed2676
commit dad62df66f
4 changed files with 227 additions and 17 deletions
+97 -1
View File
@@ -26,6 +26,10 @@ def service(tmp_path):
def photo(color="red", format="JPEG", size=(40, 20), exif=None):
if exif is None:
exif = Image.Exif()
exif[36867] = "2025:01:02 03:04:05"
exif[36881] = "+00:00"
output = io.BytesIO()
Image.new("RGB", size, color).save(output, format, **({"exif": exif} if exif else {}))
return output.getvalue()
@@ -64,8 +68,10 @@ def test_validation_is_all_or_nothing(service):
def test_multi_megabyte_multipart_file(service):
_, client, data = service
output = io.BytesIO()
exif = Image.Exif()
exif[36867] = "2025:01:02 03:04:05"
Image.frombytes("RGB", (1800, 1800), random.Random(2).randbytes(1800 * 1800 * 3)).save(
output, "JPEG", quality=95)
output, "JPEG", quality=95, exif=exif)
assert len(output.getvalue()) > 3 * 1024 * 1024
response = upload(client, [output.getvalue()])
assert response.status_code == 202, response.json
@@ -138,6 +144,7 @@ def test_orientation_metadata_and_heic(service):
exif = Image.Exif()
exif[274] = 6
exif[270] = "private metadata"
exif[36867] = "2025:01:02 03:04:05"
for raw in [photo(exif=exif, size=(3000, 1500)), photo(format="HEIF")]:
response = upload(client, [raw])
assert response.status_code == 202
@@ -149,6 +156,61 @@ def test_orientation_metadata_and_heic(service):
assert image.height > image.width
def test_photo_date_comes_from_original_metadata(service):
_, client, data = service
exif = Image.Exif()
exif[36867] = "2025:04:18 14:23:11"
exif[36881] = "+02:00"
response = upload(client, [photo(exif=exif)])
assert response.status_code == 202
entry = (data / "submissions" / response.json["id"] / "entries/00.md").read_text()
assert 'date: "2025-04-18T14:23:11+02:00"' in entry
def test_photo_dates_can_come_from_photos_library_metadata(service):
_, client, data = service
response = client.post("/api/photos", headers=AUTH, data={
"photos": [
(io.BytesIO(photo("red")), "first.jpg"),
(io.BytesIO(photo("blue")), "second.jpg"),
],
"photo_dates": "2025-04-18T14:23:11+02:00\n2025-04-19T09:10:05+02:00",
})
assert response.status_code == 202
entries = sorted((data / "submissions" / response.json["id"] / "entries").glob("*.md"))
assert 'date: "2025-04-18T14:23:11+02:00"' in entries[0].read_text()
assert 'date: "2025-04-19T09:10:05+02:00"' in entries[1].read_text()
invalid = client.post("/api/photos", headers=AUTH, data={
"photos": [(io.BytesIO(photo()), "photo.jpg")],
"photo_dates": "not-a-date",
})
assert invalid.status_code == 400
assert "valid ISO dates" in invalid.json["error"]
missing = upload(client, [photo(exif=Image.Exif())])
assert missing.status_code == 400
assert "capture date is missing" in missing.json["error"]
def test_dated_retry_replaces_the_same_undated_upload(service):
_, client, data = service
raw = photo()
original = upload(client, [raw], "Same caption", highlight="Trip").json
replacement = client.post("/api/photos", headers=AUTH, data={
"photos": [(io.BytesIO(raw), "photo.jpg")],
"caption": "Same caption",
"highlight": "Trip",
"photo_dates": "2025-04-18T14:23:11+02:00",
}).json
assert replacement["id"] != original["id"]
manifest = json.loads((data / "submissions" / replacement["id"] / "manifest.json").read_text())
assert manifest["replaces"] == original["id"]
def test_optional_highlight_preserves_existing_upload_ids(service):
_, client, data = service
raw = photo()
@@ -239,6 +301,40 @@ def test_git_push_and_crash_recovery(publisher, service, monkeypatch, tmp_path):
assert client.get(response.json["status_url"], headers=AUTH).json["status"] == "published"
def test_publisher_names_photo_entry_from_capture_date(publisher, service):
run, remote, client = publisher
exif = Image.Exif()
exif[36867] = "2025:04:18 14:23:11"
response = upload(client, [photo(exif=exif)])
result = run()
assert result.returncode == 0, result.stderr
paths = git(remote, "ls-tree", "-r", "--name-only", "master")
assert f'_art/2025-04-18-photo-{response.json["id"]}-00.md' in paths
def test_publisher_replaces_an_undated_version_of_the_same_photos(publisher, service):
run, remote, client = publisher
raw = photo()
original = upload(client, [raw], "Same caption", highlight="Trip").json
assert run().returncode == 0
replacement = client.post("/api/photos", headers=AUTH, data={
"photos": [(io.BytesIO(raw), "photo.jpg")],
"caption": "Same caption",
"highlight": "Trip",
"photo_dates": "2025-04-18T14:23:11+02:00",
}).json
result = run()
assert result.returncode == 0, result.stderr
paths = git(remote, "ls-tree", "-r", "--name-only", "master")
assert f"photo-{original['id']}" not in paths
assert f"img/arts/uploads/{original['id']}" not in paths
assert f'_art/2025-04-18-photo-{replacement["id"]}-00.md' in paths
def test_rejected_push_can_retry(publisher):
run, remote, client = publisher
head = git(remote, "rev-parse", "master")