Passed
Pull Request — master (#548)
by Alejandro
06:02
created

CreateShortUrlActionTest::provideIdn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ShlinkioApiTest\Shlink\Rest\Action;
6
7
use Cake\Chronos\Chronos;
8
use GuzzleHttp\RequestOptions;
9
use Shlinkio\Shlink\Rest\Util\RestUtils;
10
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
11
12
use function Functional\map;
13
use function range;
14
15
class CreateShortUrlActionTest extends ApiTestCase
16
{
17
    /** @test */
18
    public function createsNewShortUrlWhenOnlyLongUrlIsProvided(): void
19
    {
20
        $expectedKeys = ['shortCode', 'shortUrl', 'longUrl', 'dateCreated', 'visitsCount', 'tags'];
21
        [$statusCode, $payload] = $this->createShortUrl();
22
23
        $this->assertEquals(self::STATUS_OK, $statusCode);
24
        foreach ($expectedKeys as $key) {
25
            $this->assertArrayHasKey($key, $payload);
26
        }
27
    }
28
29
    /** @test */
30
    public function createsNewShortUrlWithCustomSlug(): void
31
    {
32
        [$statusCode, $payload] = $this->createShortUrl(['customSlug' => 'my cool slug']);
33
34
        $this->assertEquals(self::STATUS_OK, $statusCode);
35
        $this->assertEquals('my-cool-slug', $payload['shortCode']);
36
    }
37
38
    /**
39
     * @test
40
     * @dataProvider provideConflictingSlugs
41
     */
42
    public function failsToCreateShortUrlWithDuplicatedSlug(string $slug, ?string $domain): void
43
    {
44
        [$statusCode, $payload] = $this->createShortUrl(['customSlug' => $slug, 'domain' => $domain]);
45
46
        $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
47
        $this->assertEquals(RestUtils::INVALID_SLUG_ERROR, $payload['error']);
48
    }
49
50
    /** @test */
51
    public function createsNewShortUrlWithTags(): void
52
    {
53
        [$statusCode, ['tags' => $tags]] = $this->createShortUrl(['tags' => ['foo', 'bar', 'baz']]);
54
55
        $this->assertEquals(self::STATUS_OK, $statusCode);
56
        $this->assertEquals(['foo', 'bar', 'baz'], $tags);
57
    }
58
59
    /**
60
     * @test
61
     * @dataProvider provideMaxVisits
62
     */
63
    public function createsNewShortUrlWithVisitsLimit(int $maxVisits): void
64
    {
65
        [$statusCode, ['shortCode' => $shortCode]] = $this->createShortUrl(['maxVisits' => $maxVisits]);
66
67
        $this->assertEquals(self::STATUS_OK, $statusCode);
68
69
        // Last request to the short URL will return a 404, and the rest, a 302
70
        for ($i = 0; $i < $maxVisits; $i++) {
71
            $this->assertEquals(self::STATUS_FOUND, $this->callShortUrl($shortCode)->getStatusCode());
72
        }
73
        $lastResp = $this->callShortUrl($shortCode);
74
        $this->assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode());
75
    }
76
77
    public function provideMaxVisits(): array
78
    {
79
        return map(range(10, 15), function (int $i) {
80
            return [$i];
81
        });
82
    }
83
84
    /** @test */
85
    public function createsShortUrlWithValidSince(): void
86
    {
87
        [$statusCode, ['shortCode' => $shortCode]] = $this->createShortUrl([
88
            'validSince' => Chronos::now()->addDay()->toAtomString(),
89
        ]);
90
91
        $this->assertEquals(self::STATUS_OK, $statusCode);
92
93
        // Request to the short URL will return a 404 since it's not valid yet
94
        $lastResp = $this->callShortUrl($shortCode);
95
        $this->assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode());
96
    }
97
98
    /** @test */
99
    public function createsShortUrlWithValidUntil(): void
100
    {
101
        [$statusCode, ['shortCode' => $shortCode]] = $this->createShortUrl([
102
            'validUntil' => Chronos::now()->subDay()->toAtomString(),
103
        ]);
104
105
        $this->assertEquals(self::STATUS_OK, $statusCode);
106
107
        // Request to the short URL will return a 404 since it's no longer valid
108
        $lastResp = $this->callShortUrl($shortCode);
109
        $this->assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode());
110
    }
111
112
    /**
113
     * @test
114
     * @dataProvider provideMatchingBodies
115
     */
116
    public function returnsAnExistingShortUrlWhenRequested(array $body): void
117
    {
118
        [$firstStatusCode, ['shortCode' => $firstShortCode]] = $this->createShortUrl($body);
119
120
        $body['findIfExists'] = true;
121
        [$secondStatusCode, ['shortCode' => $secondShortCode]] = $this->createShortUrl($body);
122
123
        $this->assertEquals(self::STATUS_OK, $firstStatusCode);
124
        $this->assertEquals(self::STATUS_OK, $secondStatusCode);
125
        $this->assertEquals($firstShortCode, $secondShortCode);
126
    }
127
128
    public function provideMatchingBodies(): iterable
129
    {
130
        $longUrl = 'https://www.alejandrocelaya.com';
131
132
        yield 'only long URL' => [['longUrl' => $longUrl]];
133
        yield 'long URL and tags' => [['longUrl' => $longUrl, 'tags' => ['boo', 'far']]];
134
        yield 'long URL and custom slug' => [['longUrl' => $longUrl, 'customSlug' => 'my cool slug']];
135
        yield 'several params' => [[
136
            'longUrl' => $longUrl,
137
            'tags' => ['boo', 'far'],
138
            'validSince' => Chronos::now()->toAtomString(),
139
            'maxVisits' => 7,
140
        ]];
141
    }
142
143
    /**
144
     * @test
145
     * @dataProvider provideConflictingSlugs
146
     */
147
    public function returnsErrorWhenRequestingReturnExistingButCustomSlugIsInUse(string $slug, ?string $domain): void
148
    {
149
        $longUrl = 'https://www.alejandrocelaya.com';
150
151
        [$firstStatusCode] = $this->createShortUrl(['longUrl' => $longUrl]);
152
        [$secondStatusCode] = $this->createShortUrl([
153
            'longUrl' => $longUrl,
154
            'customSlug' => $slug,
155
            'findIfExists' => true,
156
            'domain' => $domain,
157
        ]);
158
159
        $this->assertEquals(self::STATUS_OK, $firstStatusCode);
160
        $this->assertEquals(self::STATUS_BAD_REQUEST, $secondStatusCode);
161
    }
162
163
    public function provideConflictingSlugs(): iterable
164
    {
165
        yield 'without domain' => ['custom', null];
166
        yield 'with domain' => ['custom-with-domain', 'some-domain.com'];
167
    }
168
169
    /** @test */
170
    public function createsNewShortUrlIfRequestedToFindButThereIsNoMatch(): void
171
    {
172
        [$firstStatusCode, ['shortCode' => $firstShortCode]] = $this->createShortUrl([
173
            'longUrl' => 'https://www.alejandrocelaya.com',
174
        ]);
175
        [$secondStatusCode, ['shortCode' => $secondShortCode]] = $this->createShortUrl([
176
            'longUrl' => 'https://www.alejandrocelaya.com/projects',
177
            'findIfExists' => true,
178
        ]);
179
180
        $this->assertEquals(self::STATUS_OK, $firstStatusCode);
181
        $this->assertEquals(self::STATUS_OK, $secondStatusCode);
182
        $this->assertNotEquals($firstShortCode, $secondShortCode);
183
    }
184
185
    /**
186
     * @test
187
     * @dataProvider provideIdn
188
     */
189
    public function createsNewShortUrlWithInternationalizedDomainName(string $longUrl): void
190
    {
191
        [$statusCode, ['longUrl' => $expectedLongUrl]] = $this->createShortUrl(['longUrl' => $longUrl]);
192
193
        $this->assertEquals(self::STATUS_OK, $statusCode);
194
        $this->assertEquals($expectedLongUrl, $longUrl);
195
    }
196
197
    public function provideIdn(): iterable
198
    {
199
        // TODO Create some shlink IDN domains to test this instead of using public ones
200
        return [['https://cédric.laubacher.io/'], ['https://laubacher.io/']]; // Second one redirects to first
201
    }
202
203
    /**
204
     * @return array {
205
     *     @var int $statusCode
206
     *     @var array $payload
207
     * }
208
     */
209
    private function createShortUrl(array $body = []): array
210
    {
211
        if (! isset($body['longUrl'])) {
212
            $body['longUrl'] = 'https://app.shlink.io';
213
        }
214
        $resp = $this->callApiWithKey(self::METHOD_POST, '/short-urls', [RequestOptions::JSON => $body]);
215
        $payload = $this->getJsonResponsePayload($resp);
216
217
        return [$resp->getStatusCode(), $payload];
218
    }
219
}
220