music on atproto
plyr.fm
1"""tests for teal.fm scrobbling integration."""
2
3from backend._internal.atproto.teal import (
4 build_teal_play_record,
5 build_teal_status_record,
6)
7from backend.config import TealSettings, settings
8
9
10class TestBuildTealPlayRecord:
11 """tests for build_teal_play_record."""
12
13 def test_builds_minimal_record(self):
14 """should build record with required fields only."""
15 record = build_teal_play_record(
16 track_name="Test Track",
17 artist_name="Test Artist",
18 )
19
20 assert record["$type"] == settings.teal.play_collection
21 assert record["trackName"] == "Test Track"
22 assert record["artists"] == [{"artistName": "Test Artist"}]
23 assert record["musicServiceBaseDomain"] == "plyr.fm"
24 assert record["submissionClientAgent"] == "plyr.fm/1.0"
25 assert "playedTime" in record
26
27 def test_includes_optional_fields(self):
28 """should include optional fields when provided."""
29 record = build_teal_play_record(
30 track_name="Test Track",
31 artist_name="Test Artist",
32 duration=180,
33 album_name="Test Album",
34 origin_url="https://plyr.fm/track/123",
35 )
36
37 assert record["duration"] == 180
38 assert record["releaseName"] == "Test Album"
39 assert record["originUrl"] == "https://plyr.fm/track/123"
40
41
42class TestBuildTealStatusRecord:
43 """tests for build_teal_status_record."""
44
45 def test_builds_status_record(self):
46 """should build status record with item."""
47 record = build_teal_status_record(
48 track_name="Now Playing",
49 artist_name="Cool Artist",
50 )
51
52 assert record["$type"] == settings.teal.status_collection
53 assert "time" in record
54 assert "expiry" in record
55 assert "item" in record
56
57 item = record["item"]
58 assert item["trackName"] == "Now Playing"
59 assert item["artists"] == [{"artistName": "Cool Artist"}]
60
61 def test_expiry_is_after_time(self):
62 """should set expiry after time."""
63 record = build_teal_status_record(
64 track_name="Test",
65 artist_name="Test",
66 )
67
68 # expiry should be 10 minutes after time
69 assert record["expiry"] > record["time"]
70
71
72class TestTealSettings:
73 """tests for teal.fm settings configuration."""
74
75 def test_default_play_collection(self):
76 """should have correct default teal play collection."""
77 assert settings.teal.play_collection == "fm.teal.alpha.feed.play"
78
79 def test_default_status_collection(self):
80 """should have correct default teal status collection."""
81 assert settings.teal.status_collection == "fm.teal.alpha.actor.status"
82
83 def test_default_enabled(self):
84 """should be enabled by default."""
85 assert settings.teal.enabled is True
86
87 def test_resolved_scope_with_teal(self):
88 """should include teal scopes in extended scope."""
89 scope = settings.atproto.resolved_scope_with_teal(
90 settings.teal.play_collection, settings.teal.status_collection
91 )
92
93 assert "fm.teal.alpha.feed.play" in scope
94 assert "fm.teal.alpha.actor.status" in scope
95 # should also include base scopes
96 assert settings.atproto.track_collection in scope
97
98 def test_env_override_play_collection(self, monkeypatch):
99 """should allow overriding play collection via environment variable.
100
101 this proves we can adapt when teal.fm changes namespaces
102 (e.g., from alpha to stable: fm.teal.feed.play).
103 """
104 monkeypatch.setenv("TEAL_PLAY_COLLECTION", "fm.teal.feed.play")
105
106 # create fresh settings to pick up env var
107 teal = TealSettings()
108 assert teal.play_collection == "fm.teal.feed.play"
109
110 def test_env_override_status_collection(self, monkeypatch):
111 """should allow overriding status collection via environment variable."""
112 monkeypatch.setenv("TEAL_STATUS_COLLECTION", "fm.teal.actor.status")
113
114 teal = TealSettings()
115 assert teal.status_collection == "fm.teal.actor.status"
116
117 def test_env_override_enabled(self, monkeypatch):
118 """should allow disabling teal integration via environment variable."""
119 monkeypatch.setenv("TEAL_ENABLED", "false")
120
121 teal = TealSettings()
122 assert teal.enabled is False
123
124 def test_scope_uses_configured_collections(self, monkeypatch):
125 """should use configured collections in OAuth scope.
126
127 when teal.fm graduates from alpha, we can update via env vars
128 without code changes.
129 """
130 monkeypatch.setenv("TEAL_PLAY_COLLECTION", "fm.teal.feed.play")
131 monkeypatch.setenv("TEAL_STATUS_COLLECTION", "fm.teal.actor.status")
132
133 teal = TealSettings()
134 scope = settings.atproto.resolved_scope_with_teal(
135 teal.play_collection, teal.status_collection
136 )
137
138 assert "fm.teal.feed.play" in scope
139 assert "fm.teal.actor.status" in scope
140 # alpha namespaces should NOT be in scope when overridden
141 assert "fm.teal.alpha.feed.play" not in scope
142 assert "fm.teal.alpha.actor.status" not in scope