Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpParity\Tests\Unit\Concerns;
4
5use SocialDept\AtpParity\MapperRegistry;
6use SocialDept\AtpParity\Tests\Fixtures\TestMapper;
7use SocialDept\AtpParity\Tests\Fixtures\TestModel;
8use SocialDept\AtpParity\Tests\Fixtures\TestRecord;
9use SocialDept\AtpParity\Tests\TestCase;
10
11class HasAtpRecordTest extends TestCase
12{
13 public function test_get_atp_uri_returns_uri_from_column(): void
14 {
15 $model = new TestModel(['atp_uri' => 'at://did:plc:test/app.test.record/abc123']);
16
17 $this->assertSame('at://did:plc:test/app.test.record/abc123', $model->getAtpUri());
18 }
19
20 public function test_get_atp_uri_returns_null_when_not_set(): void
21 {
22 $model = new TestModel();
23
24 $this->assertNull($model->getAtpUri());
25 }
26
27 public function test_get_atp_cid_returns_cid_from_column(): void
28 {
29 $model = new TestModel(['atp_cid' => 'bafyreiabc123']);
30
31 $this->assertSame('bafyreiabc123', $model->getAtpCid());
32 }
33
34 public function test_get_atp_cid_returns_null_when_not_set(): void
35 {
36 $model = new TestModel();
37
38 $this->assertNull($model->getAtpCid());
39 }
40
41 public function test_get_atp_did_extracts_did_from_uri(): void
42 {
43 $model = new TestModel(['atp_uri' => 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123']);
44
45 $this->assertSame('did:plc:z72i7hdynmk6r22z27h6tvur', $model->getAtpDid());
46 }
47
48 public function test_get_atp_did_returns_null_when_no_uri(): void
49 {
50 $model = new TestModel();
51
52 $this->assertNull($model->getAtpDid());
53 }
54
55 public function test_get_atp_did_returns_null_for_malformed_uri(): void
56 {
57 $model = new TestModel(['atp_uri' => 'invalid-uri']);
58
59 $this->assertNull($model->getAtpDid());
60 }
61
62 public function test_get_atp_collection_extracts_collection_from_uri(): void
63 {
64 $model = new TestModel(['atp_uri' => 'at://did:plc:test/app.bsky.feed.post/abc123']);
65
66 $this->assertSame('app.bsky.feed.post', $model->getAtpCollection());
67 }
68
69 public function test_get_atp_collection_returns_null_when_no_uri(): void
70 {
71 $model = new TestModel();
72
73 $this->assertNull($model->getAtpCollection());
74 }
75
76 public function test_get_atp_rkey_extracts_rkey_from_uri(): void
77 {
78 $model = new TestModel(['atp_uri' => 'at://did:plc:test/app.bsky.feed.post/3kj2h4k5j']);
79
80 $this->assertSame('3kj2h4k5j', $model->getAtpRkey());
81 }
82
83 public function test_get_atp_rkey_returns_null_when_no_uri(): void
84 {
85 $model = new TestModel();
86
87 $this->assertNull($model->getAtpRkey());
88 }
89
90 public function test_has_atp_record_returns_true_when_uri_set(): void
91 {
92 $model = new TestModel(['atp_uri' => 'at://did/col/rkey']);
93
94 $this->assertTrue($model->hasAtpRecord());
95 }
96
97 public function test_has_atp_record_returns_false_when_no_uri(): void
98 {
99 $model = new TestModel();
100
101 $this->assertFalse($model->hasAtpRecord());
102 }
103
104 public function test_get_atp_mapper_returns_mapper_when_registered(): void
105 {
106 $registry = app(MapperRegistry::class);
107 $registry->register(new TestMapper());
108
109 $model = new TestModel();
110 $mapper = $model->getAtpMapper();
111
112 $this->assertInstanceOf(TestMapper::class, $mapper);
113 }
114
115 public function test_get_atp_mapper_returns_null_when_not_registered(): void
116 {
117 // Fresh registry without any mappers
118 $this->app->forgetInstance(MapperRegistry::class);
119 $this->app->singleton(MapperRegistry::class);
120
121 $model = new TestModel();
122
123 $this->assertNull($model->getAtpMapper());
124 }
125
126 public function test_to_atp_record_converts_model_to_record(): void
127 {
128 $registry = app(MapperRegistry::class);
129 $registry->register(new TestMapper());
130
131 $model = new TestModel(['content' => 'Hello world']);
132 $record = $model->toAtpRecord();
133
134 $this->assertInstanceOf(TestRecord::class, $record);
135 $this->assertSame('Hello world', $record->text);
136 }
137
138 public function test_to_atp_record_returns_null_when_no_mapper(): void
139 {
140 $this->app->forgetInstance(MapperRegistry::class);
141 $this->app->singleton(MapperRegistry::class);
142
143 $model = new TestModel(['content' => 'Hello']);
144
145 $this->assertNull($model->toAtpRecord());
146 }
147
148 public function test_scope_with_atp_record_filters_synced_models(): void
149 {
150 TestModel::create(['content' => 'Synced', 'atp_uri' => 'at://did/col/rkey1']);
151 TestModel::create(['content' => 'Not synced']);
152 TestModel::create(['content' => 'Also synced', 'atp_uri' => 'at://did/col/rkey2']);
153
154 $synced = TestModel::withAtpRecord()->get();
155
156 $this->assertCount(2, $synced);
157 $this->assertTrue($synced->every(fn ($m) => $m->atp_uri !== null));
158 }
159
160 public function test_scope_without_atp_record_filters_unsynced_models(): void
161 {
162 TestModel::create(['content' => 'Synced', 'atp_uri' => 'at://did/col/rkey']);
163 TestModel::create(['content' => 'Not synced 1']);
164 TestModel::create(['content' => 'Not synced 2']);
165
166 $unsynced = TestModel::withoutAtpRecord()->get();
167
168 $this->assertCount(2, $unsynced);
169 $this->assertTrue($unsynced->every(fn ($m) => $m->atp_uri === null));
170 }
171
172 public function test_scope_where_atp_uri_finds_by_uri(): void
173 {
174 TestModel::create(['content' => 'Target', 'atp_uri' => 'at://did/col/target']);
175 TestModel::create(['content' => 'Other', 'atp_uri' => 'at://did/col/other']);
176
177 $found = TestModel::whereAtpUri('at://did/col/target')->first();
178
179 $this->assertNotNull($found);
180 $this->assertSame('Target', $found->content);
181 }
182
183 public function test_scope_where_atp_uri_returns_null_when_not_found(): void
184 {
185 TestModel::create(['content' => 'Some', 'atp_uri' => 'at://did/col/some']);
186
187 $found = TestModel::whereAtpUri('at://did/col/nonexistent')->first();
188
189 $this->assertNull($found);
190 }
191}