Completed
Push — master ( a81ac8...05e307 )
by Alejandro
25s queued 13s
created

provideConflictingSlugs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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