Skip to content

Commit aac33c5

Browse files
committed
test: add http tests
1 parent 53f7d26 commit aac33c5

15 files changed

Lines changed: 862 additions & 143 deletions

.github/workflows/lint.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Lint code
2+
3+
on: ["push","pull_request"]
4+
5+
jobs:
6+
lint:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@v4
12+
with:
13+
ref: ${{ github.head_ref }}
14+
15+
- name: Setup PHP
16+
uses: shivammathur/setup-php@v2
17+
with:
18+
php-version: '8.2'
19+
extensions: json, zip, curl
20+
tools: composer:v2
21+
coverage: none
22+
23+
- name: Install PHP dependencies
24+
run: composer update --no-interaction --no-progress
25+
26+
- name: Run Linter
27+
run: composer run lint -- --check

.github/workflows/tests.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Run Tests
2+
3+
on: ["push","pull_request"]
4+
5+
jobs:
6+
tests:
7+
runs-on: ${{ matrix.os }}
8+
strategy:
9+
fail-fast: true
10+
matrix:
11+
os: ["macos-latest","ubuntu-latest","windows-latest"]
12+
php: ["8.4","8.3","8.2"]
13+
14+
name: PHP ${{ matrix.php }} - ${{ matrix.os }}
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Setup PHP
21+
uses: shivammathur/setup-php@v2
22+
with:
23+
php-version: ${{ matrix.php }}
24+
extensions: json, zip, curl
25+
tools: composer:v2
26+
coverage: xdebug
27+
28+
- name: Install PHP dependencies
29+
run: composer update --no-interaction --no-progress
30+
31+
- name: Run Tests
32+
run: composer run test -- --flags=coverage

alchemy.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
app:
2+
- src
3+
4+
tests:
5+
engine: pest
6+
parallel: false
7+
paths:
8+
- tests
9+
files:
10+
- '*.test.php'
11+
coverage:
12+
processUncoveredFiles: true
13+
14+
lint:
15+
preset: PSR12
16+
rules:
17+
single_quote: true
18+
phpdoc_scalar: true
19+
no_unused_imports: true
20+
unary_operator_spaces: true
21+
binary_operator_spaces: true
22+
phpdoc_var_without_name: true
23+
trailing_comma_in_multiline: true
24+
phpdoc_single_line_var_spacing: true
25+
single_trait_insert_per_statement: true
26+
not_operator_with_successor_space: false
27+
array_syntax:
28+
syntax: short
29+
ordered_imports:
30+
sort_algorithm: alpha
31+
method_argument_space:
32+
on_multiline: ensure_fully_multiline
33+
keep_multiple_spaces_after_comma: true
34+
blank_line_before_statement:
35+
statements:
36+
- try
37+
- break
38+
- throw
39+
- return
40+
- declare
41+
- continue
42+
43+
actions:
44+
run:
45+
- lint
46+
- tests
47+
os:
48+
- macos-latest
49+
- ubuntu-latest
50+
- windows-latest
51+
php:
52+
extensions: json, zip, curl
53+
versions: # library supports ^7.4|^8.0, but pest 3 needs 8.2+
54+
- '8.4'
55+
- '8.3'
56+
- '8.2'
57+
events:
58+
- push
59+
- pull_request

composer.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,21 @@
4040
"branch-alias": {
4141
"dev-v5.x": "5.0.x-dev"
4242
}
43+
},
44+
"require-dev": {
45+
"friendsofphp/php-cs-fixer": "^3.95",
46+
"pestphp/pest": "*",
47+
"leafs/alchemy": "v5.x-dev"
48+
},
49+
"config": {
50+
"allow-plugins": {
51+
"pestphp/pest-plugin": true
52+
}
53+
},
54+
"scripts": {
55+
"test": "./vendor/bin/alchemy test",
56+
"lint": "./vendor/bin/alchemy lint",
57+
"fmt": "./vendor/bin/alchemy fmt",
58+
"ci": "./vendor/bin/alchemy ci"
4359
}
44-
}
60+
}

src/Cache.php

Lines changed: 80 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -6,97 +6,99 @@
66
* Leaf Http Caching
77
* ------------------------------------
88
* HTTP Caching made simple with Leaf
9-
*
9+
*
1010
* @author Michael Darko
1111
* @since 3.0.0
1212
*/
1313
class Cache
1414
{
15-
/**
16-
* Set Last-Modified HTTP Response Header
17-
*
18-
* Set the HTTP 'Last-Modified' header and stop if a conditional
19-
* GET request's `If-Modified-Since` header matches the last modified time
20-
* of the resource. The `time` argument is a UNIX timestamp integer value.
21-
* When the current request includes an 'If-Modified-Since' header that
22-
* matches the specified last modified time, the application will stop
23-
* and send a '304 Not Modified' response to the client.
24-
*
25-
* @param int $time The last modified UNIX timestamp
26-
*/
27-
public static function lastModified(int $time)
28-
{
29-
Headers::set('Last-Modified', gmdate('D, d M Y H:i:s T', $time));
15+
/**
16+
* Set Last-Modified HTTP Response Header
17+
*
18+
* Set the HTTP 'Last-Modified' header and stop if a conditional
19+
* GET request's `If-Modified-Since` header matches the last modified time
20+
* of the resource. The `time` argument is a UNIX timestamp integer value.
21+
* When the current request includes an 'If-Modified-Since' header that
22+
* matches the specified last modified time, the application will stop
23+
* and send a '304 Not Modified' response to the client.
24+
*
25+
* @param int $time The last modified UNIX timestamp
26+
*/
27+
public static function lastModified(int $time)
28+
{
29+
Headers::set('Last-Modified', gmdate('D, d M Y H:i:s T', $time));
3030

31-
if ($time === strtotime(Headers::get('If-Modified-Since'))) {
32-
\Leaf\App::halt(304);
33-
}
34-
}
31+
$ifModifiedSince = Headers::get('If-Modified-Since');
3532

36-
/**
37-
* Set ETag HTTP Response Header
38-
*
39-
* Set the etag header and stop if the conditional GET request matches.
40-
* The `value` argument is a unique identifier for the current resource.
41-
* The `type` argument indicates whether the etag should be used as a strong or
42-
* weak cache validator.
43-
*
44-
* When the current request includes an 'If-None-Match' header with
45-
* a matching etag, execution is immediately stopped. If the request
46-
* method is GET or HEAD, a '304 Not Modified' response is sent.
47-
*
48-
* @param string $value The etag value
49-
* @param string $type The type of etag to create; either "strong" or "weak"
50-
*/
51-
public static function etag(string $value, string $type = "strong")
52-
{
53-
if (!in_array($type, ["strong", "weak"])) {
54-
trigger_error("Invalid Leaf::etag type. Expected either \"strong\" or \"weak\".");
55-
}
33+
if ($ifModifiedSince && $time === strtotime($ifModifiedSince)) {
34+
\Leaf\App::halt(304);
35+
}
36+
}
5637

57-
$value = "\"$value\"";
38+
/**
39+
* Set ETag HTTP Response Header
40+
*
41+
* Set the etag header and stop if the conditional GET request matches.
42+
* The `value` argument is a unique identifier for the current resource.
43+
* The `type` argument indicates whether the etag should be used as a strong or
44+
* weak cache validator.
45+
*
46+
* When the current request includes an 'If-None-Match' header with
47+
* a matching etag, execution is immediately stopped. If the request
48+
* method is GET or HEAD, a '304 Not Modified' response is sent.
49+
*
50+
* @param string $value The etag value
51+
* @param string $type The type of etag to create; either "strong" or "weak"
52+
*/
53+
public static function etag(string $value, string $type = 'strong')
54+
{
55+
if (!in_array($type, ['strong', 'weak'])) {
56+
trigger_error('Invalid Leaf::etag type. Expected either "strong" or "weak".');
57+
}
5858

59-
if ($type === "weak") {
60-
$value = "W/" . $value;
61-
}
59+
$value = "\"$value\"";
6260

63-
Headers::set("ETag", $value);
61+
if ($type === 'weak') {
62+
$value = 'W/' . $value;
63+
}
6464

65-
if ($etagsHeader = Headers::get("If-None-Match")) {
66-
$etags = preg_split("@\s*,\s*@", $etagsHeader);
65+
Headers::set('ETag', $value);
6766

68-
if (in_array($value, $etags) || in_array("*", $etags)) {
69-
$_304Methods = [Request::METHOD_GET, Request::METHOD_HEAD];
67+
if ($etagsHeader = Headers::get('If-None-Match')) {
68+
$etags = preg_split("@\s*,\s*@", $etagsHeader);
7069

71-
if (in_array(Request::getMethod(), $_304Methods)) {
72-
\Leaf\App::halt(304);
73-
} else {
74-
// according to https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
75-
// all methods besides GET and HEAD should return a 421 (Precondition Failed)
76-
\Leaf\App::halt(412);
77-
}
78-
}
79-
}
80-
}
70+
if (in_array($value, $etags) || in_array('*', $etags)) {
71+
$_304Methods = [Request::METHOD_GET, Request::METHOD_HEAD];
8172

82-
/**
83-
* Set Expires HTTP response header
84-
*
85-
* The `Expires` header tells the HTTP client the time at which
86-
* the current resource should be considered stale. At that time the HTTP
87-
* client will send a conditional GET request to the server; the server
88-
* may return a 200 OK if the resource has changed, else a 304 Not Modified
89-
* if the resource has not changed. The `Expires` header should be used in
90-
* conjunction with the `etag()` or `lastModified()` methods above.
91-
*
92-
* @param string|int $time If string, a time to be parsed by `strtotime()`; If int, a UNIX timestamp;
93-
*/
94-
public static function expires($time)
95-
{
96-
if (is_string($time)) {
97-
$time = strtotime($time);
98-
}
73+
if (in_array(Request::getMethod(), $_304Methods)) {
74+
\Leaf\App::halt(304);
75+
} else {
76+
// according to https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
77+
// all methods besides GET and HEAD should return a 421 (Precondition Failed)
78+
\Leaf\App::halt(412);
79+
}
80+
}
81+
}
82+
}
9983

100-
Headers::set('Expires', gmdate('D, d M Y H:i:s T', $time));
101-
}
84+
/**
85+
* Set Expires HTTP response header
86+
*
87+
* The `Expires` header tells the HTTP client the time at which
88+
* the current resource should be considered stale. At that time the HTTP
89+
* client will send a conditional GET request to the server; the server
90+
* may return a 200 OK if the resource has changed, else a 304 Not Modified
91+
* if the resource has not changed. The `Expires` header should be used in
92+
* conjunction with the `etag()` or `lastModified()` methods above.
93+
*
94+
* @param string|int $time If string, a time to be parsed by `strtotime()`; If int, a UNIX timestamp;
95+
*/
96+
public static function expires($time)
97+
{
98+
if (is_string($time)) {
99+
$time = strtotime($time);
100+
}
101+
102+
Headers::set('Expires', gmdate('D, d M Y H:i:s T', $time));
103+
}
102104
}

0 commit comments

Comments
 (0)