Skip to content

Commit a05390f

Browse files
test: add unit tests for Link model methods (#22)
1 parent fd5e25d commit a05390f

1 file changed

Lines changed: 322 additions & 0 deletions

File tree

tests/Unit/LinkTest.php

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use App\Models\Link;
6+
use Carbon\Carbon;
7+
use Tests\TestCase;
8+
9+
class LinkTest extends TestCase
10+
{
11+
// -------------------------------------------------------------------------
12+
// normalizeTags
13+
// -------------------------------------------------------------------------
14+
15+
public function test_normalize_tags_returns_null_for_empty_string(): void
16+
{
17+
$this->assertNull(Link::normalizeTags(''));
18+
}
19+
20+
public function test_normalize_tags_returns_null_for_empty_array(): void
21+
{
22+
$this->assertNull(Link::normalizeTags([]));
23+
}
24+
25+
public function test_normalize_tags_returns_null_for_null(): void
26+
{
27+
$this->assertNull(Link::normalizeTags(null));
28+
}
29+
30+
public function test_normalize_tags_splits_comma_separated_string(): void
31+
{
32+
$result = Link::normalizeTags('marketing,sales,growth');
33+
34+
$this->assertSame(['marketing', 'sales', 'growth'], $result);
35+
}
36+
37+
public function test_normalize_tags_splits_by_multiple_delimiters(): void
38+
{
39+
$result = Link::normalizeTags("foo,bar;baz|qux\ncorge");
40+
41+
$this->assertSame(['foo', 'bar', 'baz', 'qux', 'corge'], $result);
42+
}
43+
44+
public function test_normalize_tags_lowercases_tags(): void
45+
{
46+
$result = Link::normalizeTags('Marketing,SALES');
47+
48+
$this->assertSame(['marketing', 'sales'], $result);
49+
}
50+
51+
public function test_normalize_tags_strips_special_characters(): void
52+
{
53+
$result = Link::normalizeTags('hello@world!,foo#bar');
54+
55+
$this->assertSame(['helloworld', 'foobar'], $result);
56+
}
57+
58+
public function test_normalize_tags_deduplicates(): void
59+
{
60+
$result = Link::normalizeTags('php,laravel,php');
61+
62+
$this->assertSame(['php', 'laravel'], $result);
63+
}
64+
65+
public function test_normalize_tags_caps_at_ten(): void
66+
{
67+
$input = 'a,b,c,d,e,f,g,h,i,j,k,l';
68+
69+
$result = Link::normalizeTags($input);
70+
71+
$this->assertCount(10, $result);
72+
}
73+
74+
public function test_normalize_tags_truncates_each_tag_to_thirty_chars(): void
75+
{
76+
$long = str_repeat('a', 40);
77+
78+
$result = Link::normalizeTags($long);
79+
80+
$this->assertSame([str_repeat('a', 30)], $result);
81+
}
82+
83+
public function test_normalize_tags_accepts_array_input(): void
84+
{
85+
$result = Link::normalizeTags(['Laravel', 'PHP']);
86+
87+
$this->assertSame(['laravel', 'php'], $result);
88+
}
89+
90+
public function test_normalize_tags_filters_blank_entries(): void
91+
{
92+
$result = Link::normalizeTags('foo,,bar, ,baz');
93+
94+
$this->assertSame(['foo', 'bar', 'baz'], $result);
95+
}
96+
97+
// -------------------------------------------------------------------------
98+
// appendParams
99+
// -------------------------------------------------------------------------
100+
101+
public function test_append_params_returns_url_unchanged_when_params_is_empty(): void
102+
{
103+
$link = new Link(['params' => []]);
104+
105+
$this->assertSame('https://example.com', $link->appendParams('https://example.com'));
106+
}
107+
108+
public function test_append_params_returns_url_unchanged_when_params_is_null(): void
109+
{
110+
$link = new Link;
111+
112+
$this->assertSame('https://example.com', $link->appendParams('https://example.com'));
113+
}
114+
115+
public function test_append_params_appends_params_to_clean_url(): void
116+
{
117+
$link = new Link(['params' => ['utm_source' => 'newsletter', 'utm_medium' => 'email']]);
118+
119+
$result = $link->appendParams('https://example.com/page');
120+
121+
$this->assertStringContainsString('utm_source=newsletter', $result);
122+
$this->assertStringContainsString('utm_medium=email', $result);
123+
}
124+
125+
public function test_append_params_existing_url_params_take_precedence(): void
126+
{
127+
$link = new Link(['params' => ['utm_source' => 'newsletter']]);
128+
129+
$result = $link->appendParams('https://example.com?utm_source=direct');
130+
131+
$this->assertStringContainsString('utm_source=direct', $result);
132+
$this->assertStringNotContainsString('utm_source=newsletter', $result);
133+
}
134+
135+
public function test_append_params_preserves_hash_fragment(): void
136+
{
137+
$link = new Link(['params' => ['ref' => 'home']]);
138+
139+
$result = $link->appendParams('https://example.com#section');
140+
141+
$this->assertStringContainsString('ref=home', $result);
142+
$this->assertStringEndsWith('#section', $result);
143+
}
144+
145+
public function test_append_params_merges_with_existing_query_and_keeps_hash(): void
146+
{
147+
$link = new Link(['params' => ['utm_source' => 'email', 'utm_medium' => 'campaign']]);
148+
149+
$result = $link->appendParams('https://example.com?ref=social#top');
150+
151+
$this->assertStringContainsString('ref=social', $result);
152+
$this->assertStringContainsString('utm_source=email', $result);
153+
$this->assertStringEndsWith('#top', $result);
154+
}
155+
156+
// -------------------------------------------------------------------------
157+
// isExpired
158+
// -------------------------------------------------------------------------
159+
160+
public function test_is_expired_returns_false_when_no_expiry_set(): void
161+
{
162+
$link = new Link;
163+
164+
$this->assertFalse($link->isExpired());
165+
}
166+
167+
public function test_is_expired_returns_false_when_expiry_is_in_the_future(): void
168+
{
169+
$link = new Link(['expires_at' => Carbon::now()->addDay()]);
170+
171+
$this->assertFalse($link->isExpired());
172+
}
173+
174+
public function test_is_expired_returns_true_when_expiry_is_in_the_past(): void
175+
{
176+
$link = new Link(['expires_at' => Carbon::now()->subDay()]);
177+
178+
$this->assertTrue($link->isExpired());
179+
}
180+
181+
public function test_is_expired_returns_true_when_expiry_is_exactly_now(): void
182+
{
183+
Carbon::setTestNow(Carbon::now());
184+
$link = new Link(['expires_at' => Carbon::now()->subSecond()]);
185+
186+
$this->assertTrue($link->isExpired());
187+
188+
Carbon::setTestNow();
189+
}
190+
191+
// -------------------------------------------------------------------------
192+
// isOverLimit
193+
// -------------------------------------------------------------------------
194+
195+
public function test_is_over_limit_returns_false_when_no_limit_set(): void
196+
{
197+
$link = new Link(['clicks' => 9999]);
198+
199+
$this->assertFalse($link->isOverLimit());
200+
}
201+
202+
public function test_is_over_limit_returns_false_when_clicks_are_below_limit(): void
203+
{
204+
$link = new Link(['click_limit' => 100, 'clicks' => 50]);
205+
206+
$this->assertFalse($link->isOverLimit());
207+
}
208+
209+
public function test_is_over_limit_returns_true_when_clicks_equal_limit(): void
210+
{
211+
$link = new Link(['click_limit' => 100, 'clicks' => 100]);
212+
213+
$this->assertTrue($link->isOverLimit());
214+
}
215+
216+
public function test_is_over_limit_returns_true_when_clicks_exceed_limit(): void
217+
{
218+
$link = new Link(['click_limit' => 100, 'clicks' => 101]);
219+
220+
$this->assertTrue($link->isOverLimit());
221+
}
222+
223+
public function test_is_over_limit_handles_zero_limit(): void
224+
{
225+
$link = new Link(['click_limit' => 0, 'clicks' => 0]);
226+
227+
$this->assertTrue($link->isOverLimit());
228+
}
229+
230+
// -------------------------------------------------------------------------
231+
// hasDeepLinks
232+
// -------------------------------------------------------------------------
233+
234+
public function test_has_deep_links_returns_false_when_meta_is_empty(): void
235+
{
236+
$link = new Link(['meta' => []]);
237+
238+
$this->assertFalse($link->hasDeepLinks());
239+
}
240+
241+
public function test_has_deep_links_returns_true_when_ios_is_set(): void
242+
{
243+
$link = new Link(['meta' => ['deep_link' => ['ios' => 'myapp://home']]]);
244+
245+
$this->assertTrue($link->hasDeepLinks());
246+
}
247+
248+
public function test_has_deep_links_returns_true_when_android_is_set(): void
249+
{
250+
$link = new Link(['meta' => ['deep_link' => ['android' => 'myapp://home']]]);
251+
252+
$this->assertTrue($link->hasDeepLinks());
253+
}
254+
255+
public function test_has_deep_links_returns_true_when_both_are_set(): void
256+
{
257+
$link = new Link(['meta' => ['deep_link' => ['ios' => 'myapp://home', 'android' => 'myapp://home']]]);
258+
259+
$this->assertTrue($link->hasDeepLinks());
260+
}
261+
262+
// -------------------------------------------------------------------------
263+
// deepLinkFor
264+
// -------------------------------------------------------------------------
265+
266+
public function test_deep_link_for_returns_null_for_unknown_os(): void
267+
{
268+
$link = new Link(['meta' => ['deep_link' => ['ios' => 'myapp://home']]]);
269+
270+
$this->assertNull($link->deepLinkFor('Windows'));
271+
}
272+
273+
public function test_deep_link_for_returns_null_for_desktop_os(): void
274+
{
275+
$link = new Link(['meta' => ['deep_link' => ['ios' => 'myapp://home']]]);
276+
277+
$this->assertNull($link->deepLinkFor('Windows'));
278+
}
279+
280+
public function test_deep_link_for_returns_ios_uri(): void
281+
{
282+
$link = new Link(['meta' => ['deep_link' => ['ios' => 'myapp://home']]]);
283+
284+
$this->assertSame('myapp://home', $link->deepLinkFor('iOS'));
285+
}
286+
287+
public function test_deep_link_for_returns_android_uri(): void
288+
{
289+
$link = new Link(['meta' => ['deep_link' => ['android' => 'myapp://product/42']]]);
290+
291+
$this->assertSame('myapp://product/42', $link->deepLinkFor('Android'));
292+
}
293+
294+
public function test_deep_link_for_returns_null_when_uri_is_empty_string(): void
295+
{
296+
$link = new Link(['meta' => ['deep_link' => ['ios' => ' ']]]);
297+
298+
$this->assertNull($link->deepLinkFor('iOS'));
299+
}
300+
301+
// -------------------------------------------------------------------------
302+
// cacheKey
303+
// -------------------------------------------------------------------------
304+
305+
public function test_cache_key_returns_expected_format(): void
306+
{
307+
$this->assertSame('lf:link:1:abc123', Link::cacheKey(1, 'abc123'));
308+
}
309+
310+
public function test_cache_key_is_unique_per_domain_and_alias(): void
311+
{
312+
$this->assertNotSame(
313+
Link::cacheKey(1, 'foo'),
314+
Link::cacheKey(2, 'foo')
315+
);
316+
317+
$this->assertNotSame(
318+
Link::cacheKey(1, 'foo'),
319+
Link::cacheKey(1, 'bar')
320+
);
321+
}
322+
}

0 commit comments

Comments
 (0)