1+ <?php namespace web \unittest \filters ;
2+
3+ use test \{Assert , Test , Values };
4+ use web \filters \{CORS , Origins , Invocation };
5+ use web \io \{TestInput , TestOutput };
6+ use web \{Filter , Request , Response };
7+
8+ class CORSTest {
9+ const ORIGIN = 'http://example.com ' ;
10+ const RESPONSE = ['Content-Type ' => 'text/plain ' , 'Content-Length ' => 9 ];
11+
12+ private function filter (CORS $ fixture , $ method , $ uri , $ headers = [], $ body = null ) {
13+ $ req = new Request (new TestInput ($ method , $ uri , $ headers , $ body ?? '' ));
14+ $ res = new Response (new TestOutput ());
15+ $ fixture ->filter ($ req , $ res , new Invocation (function ($ req , $ res ) {
16+ $ res ->send ('Completed ' , 'text/plain ' );
17+ }));
18+ return $ res ;
19+ }
20+
21+ /** Returns fixture with the origin set */
22+ private function fixture (): CORS {
23+ return (new CORS ())->origins (self ::ORIGIN );
24+ }
25+
26+ /** Values for preflight test */
27+ private function preflights () {
28+ yield [$ this ->fixture (), []];
29+ yield [$ this ->fixture ()->origins (function ($ origin ) { return self ::ORIGIN === $ origin ? $ origin : null ; }), []];
30+ yield [$ this ->fixture ()->origins ('* ' ), ['Access-Control-Allow-Origin ' => '* ' ]];
31+
32+ // Methods
33+ yield [$ this ->fixture ()->methods (null ), []];
34+ yield [$ this ->fixture ()->methods ([]), []];
35+ yield [$ this ->fixture ()->methods ('GET, POST ' ), ['Access-Control-Allow-Methods ' => 'GET, POST ' ]];
36+ yield [$ this ->fixture ()->methods (['GET ' , 'POST ' ]), ['Access-Control-Allow-Methods ' => 'GET, POST ' ]];
37+
38+ // Headers
39+ yield [$ this ->fixture ()->headers (null ), []];
40+ yield [$ this ->fixture ()->headers ([]), []];
41+ yield [$ this ->fixture ()->headers ('X-Input ' ), ['Access-Control-Allow-Headers ' => 'X-Input ' ]];
42+ yield [$ this ->fixture ()->headers (['X-Input ' ]), ['Access-Control-Allow-Headers ' => 'X-Input ' ]];
43+
44+ // Age
45+ yield [$ this ->fixture ()->maxAge (null ), []];
46+ yield [$ this ->fixture ()->maxAge (0 ), []];
47+ yield [$ this ->fixture ()->maxAge (86400 ), ['Access-Control-Max-Age ' => '86400 ' ]];
48+
49+ // Expose
50+ yield [$ this ->fixture ()->expose (null ), []];
51+ yield [$ this ->fixture ()->expose ([]), []];
52+ yield [$ this ->fixture ()->expose ('X-Output ' ), ['Access-Control-Expose-Headers ' => 'X-Output ' ]];
53+ yield [$ this ->fixture ()->expose (['X-Output ' ]), ['Access-Control-Expose-Headers ' => 'X-Output ' ]];
54+
55+ // Credentials
56+ yield [$ this ->fixture ()->credentials (false ), []];
57+ yield [$ this ->fixture ()->credentials (true ), ['Access-Control-Allow-Credentials ' => 'true ' ]];
58+ }
59+
60+ /** Values for request test */
61+ private function requests () {
62+ yield [$ this ->fixture (), []];
63+
64+ // Only included in preflight
65+ yield [$ this ->fixture ()->methods (['GET ' , 'POST ' ]), []];
66+ yield [$ this ->fixture ()->headers (['X-Input ' ]), []];
67+ yield [$ this ->fixture ()->maxAge (86400 ), []];
68+
69+ // Included in all requests
70+ yield [$ this ->fixture ()->expose (['X-Output ' ]), ['Access-Control-Expose-Headers ' => 'X-Output ' ]];
71+ yield [$ this ->fixture ()->credentials (true ), ['Access-Control-Allow-Credentials ' => 'true ' ]];
72+ }
73+
74+ /** Values for allowing_origin_plain_or_with_port_3000 */
75+ private function origins () {
76+ yield [self ::ORIGIN , true ];
77+ yield [self ::ORIGIN .':3000 ' , true ];
78+
79+ // Not allowed
80+ yield [self ::ORIGIN .':443 ' , false ];
81+ yield [strtr (self ::ORIGIN , ['http: ' => 'https: ' ]), false ];
82+ yield ['http://localhost ' , false ];
83+ yield ['' , false ];
84+ }
85+
86+ #[Test]
87+ public function can_create () {
88+ new CORS ();
89+ }
90+
91+ #[Test]
92+ public function request_without_origin_receives_no_cors () {
93+ $ response = $ this ->filter (new CORS (), 'GET ' , '/ ' );
94+
95+ Assert::equals (200 , $ response ->status ());
96+ Assert::equals (self ::RESPONSE , $ response ->headers ());
97+ }
98+
99+ #[Test, Values(from: 'preflights ' )]
100+ public function preflight ($ fixture , $ expected ) {
101+ $ response = $ this ->filter ($ fixture , 'OPTIONS ' , '/ ' , [
102+ 'Origin ' => self ::ORIGIN ,
103+ 'Access-Control-Request-Method ' => 'GET ' ,
104+ 'Access-Control-Request-Headers ' => 'X-Input ' ,
105+ ]);
106+
107+ Assert::equals (204 , $ response ->status ());
108+ Assert::equals (
109+ $ expected + ['Vary ' => 'Origin ' , 'Access-Control-Allow-Origin ' => self ::ORIGIN ],
110+ $ response ->headers ()
111+ );
112+ }
113+
114+ #[Test, Values(from: 'requests ' )]
115+ public function request ($ fixture , $ expected ) {
116+ $ response = $ this ->filter ($ fixture , 'GET ' , '/ ' , ['Origin ' => self ::ORIGIN ]);
117+
118+ Assert::equals (200 , $ response ->status ());
119+ Assert::equals (
120+ $ expected + ['Vary ' => 'Origin ' , 'Access-Control-Allow-Origin ' => self ::ORIGIN ] + self ::RESPONSE ,
121+ $ response ->headers ()
122+ );
123+ }
124+
125+ #[Test, Values(from: 'origins ' )]
126+ public function allowing_origin_plain_or_with_port_3000 ($ origin , $ allow ) {
127+ $ fixture = (new CORS ())->origins ((new Origins (self ::ORIGIN ))->ports ([null , 3000 ]));
128+ $ response = $ this ->filter ($ fixture , 'GET ' , '/ ' , ['Origin ' => $ origin ]);
129+
130+ Assert::equals (200 , $ response ->status ());
131+ Assert::equals (
132+ ($ allow ? ['Access-Control-Allow-Origin ' => $ origin ] : []) + ['Vary ' => 'Origin ' ] + self ::RESPONSE ,
133+ $ response ->headers ()
134+ );
135+ }
136+ }
0 commit comments