From 4a890935aff63f275e77cdab4e553df30a0bf179 Mon Sep 17 00:00:00 2001 From: Richard Muvirimi Date: Wed, 27 Sep 2023 09:25:45 +0200 Subject: [PATCH] Add api end point tests, scope tests --- app/GraphQL/Scalars/UnixTimeStamp.php | 28 ---- app/Models/Rate.php | 1 + app/Traits/ScrapesRates.php | 2 +- composer.json | 2 +- graphql/schema.graphql | 4 +- phpunit.xml | 2 +- readme.md | 5 + tests/CreatesApplication.php | 4 +- tests/Feature/ApiGraphqlTest.php | 208 +++++++++++++++++++++++ tests/Feature/ApiVersion0Test.php | 153 +++++++++++++++++ tests/Feature/ApiVersion1Test.php | 229 ++++++++++++++++++++++++++ tests/Feature/ExampleTest.php | 19 --- tests/Feature/RateScopeTest.php | 117 +++++++++++++ tests/Unit/ExampleTest.php | 2 +- 14 files changed, 721 insertions(+), 55 deletions(-) delete mode 100644 app/GraphQL/Scalars/UnixTimeStamp.php create mode 100644 tests/Feature/ApiGraphqlTest.php create mode 100644 tests/Feature/ApiVersion0Test.php create mode 100644 tests/Feature/ApiVersion1Test.php delete mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Feature/RateScopeTest.php diff --git a/app/GraphQL/Scalars/UnixTimeStamp.php b/app/GraphQL/Scalars/UnixTimeStamp.php deleted file mode 100644 index 8546484..0000000 --- a/app/GraphQL/Scalars/UnixTimeStamp.php +++ /dev/null @@ -1,28 +0,0 @@ -getTimestamp()); - } - - /** - * {@inheritDoc} - */ - protected function parse(mixed $value): Carbon - { - return Carbon::createFromTimestamp($value); - } -} diff --git a/app/Models/Rate.php b/app/Models/Rate.php index 85d79b9..274f1f1 100644 --- a/app/Models/Rate.php +++ b/app/Models/Rate.php @@ -43,6 +43,7 @@ class Rate extends Model protected $casts = [ 'status' => 'boolean', 'enabled' => 'boolean', + 'javascript' => 'boolean', 'rate' => 'float', 'rate_updated_at' => 'datetime', 'updated_at' => 'datetime', diff --git a/app/Traits/ScrapesRates.php b/app/Traits/ScrapesRates.php index bc79624..14f75b3 100644 --- a/app/Traits/ScrapesRates.php +++ b/app/Traits/ScrapesRates.php @@ -53,7 +53,7 @@ private function getHtmlContent(Rate $theRate): string 'timeout' => env('SCRAPPY_TIMEOUT'), 'user_agent' => $this->getUserAgent(), 'css' => 'body', - 'javascript' => $theRate->javascript ? 'true' : 'false', + 'javascript' => strval($theRate->javascript), ]; $options = [ diff --git a/composer.json b/composer.json index d78e0d6..e1f81d0 100755 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "vendor/bin/pint ." ], "crawl": "@php spark crawl", - "test": "php artisan test", + "test": "php artisan test --stop-on-failure", "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" diff --git a/graphql/schema.graphql b/graphql/schema.graphql index caa8329..28bf4f2 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -1,14 +1,12 @@ "A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`." scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime") -scalar UnixTimeStamp @scalar(class: "UnixTimeStamp") - "Indicates what fields are available at the top level of a query operation." type Query { rate( search: String @scope(name : "logAnalyticsEvent") @scope(name : "enabled") @scope(name : "updated") @scope(name: "search") @rules(apply: ["string"]) - date: UnixTimeStamp @scope(name : "logAnalyticsEvent") @scope(name : "enabled") @scope(name : "updated") @scope(name: "date") @rules(apply: ["numeric", "date_format:U", "before:now"]) + date: Int @scope(name : "logAnalyticsEvent") @scope(name : "enabled") @scope(name : "updated") @scope(name: "date") @rules(apply: ["numeric", "date_format:U", "before:now"]) currency: Currency @scope(name : "logAnalyticsEvent") @scope(name : "enabled") @scope(name : "updated") @scope(name: "currency") @rules(apply : ["string", "exists:rates,rate_currency"]) cors: Boolean @scope(name : "logAnalyticsEvent") @scope(name : "enabled") @scope(name : "updated") @scope(name: "cors") @rules(apply: ["boolean"]) prefer: Prefer @scope(name : "logAnalyticsEvent") @scope(name : "enabled") @scope(name : "updated") @scope(name: "preferred") @rules(apply: ["string", "in:MIN,min,MAX,max,MEAN,mean,MEDIAN,median,RANDOM,random,MODE,mode"]) diff --git a/phpunit.xml b/phpunit.xml index b4cfe10..63cc1bc 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,7 +19,7 @@ - + diff --git a/readme.md b/readme.md index c2b2948..ce01701 100755 --- a/readme.md +++ b/readme.md @@ -64,6 +64,11 @@ current days rate. 5. Visit `your-site/api` or `your-site/api/v1` for the api +### Tests + +1. Make sure the server is running `php artisan serve` +2. Run `php artisan test` + ### Contributions and Issues Contributions are more than welcome, as well as issues diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index cc68301..29a92c5 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -12,7 +12,9 @@ trait CreatesApplication */ public function createApplication(): Application { - $app = require __DIR__.'/../bootstrap/app.php'; + $app = require __DIR__ . '/../bootstrap/app.php'; + + $app->usePublicPath(dirname(__DIR__) . '/public_html'); $app->make(Kernel::class)->bootstrap(); diff --git a/tests/Feature/ApiGraphqlTest.php b/tests/Feature/ApiGraphqlTest.php new file mode 100644 index 0000000..fd96558 --- /dev/null +++ b/tests/Feature/ApiGraphqlTest.php @@ -0,0 +1,208 @@ +graphQl(/** @lang GraphQL */ 'query {USD : rate { currency last_checked last_updated name rate url }}'); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => [ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + ], + ]); + + Rate::query()->enabled()->updated()->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->rate_name, + 'rate' => $rate->rate, + 'url' => $rate->source_url, + ]); + }); + } + + /** + * Test the prefer aggregate of the api + */ + public function test_filter_prefer_aggregate_works(): void + { + $aggregate = "MEDIAN"; + + $response = $this->graphQl(/** @lang GraphQL */ 'query($prefer : Prefer!) {USD : rate(prefer : $prefer) { currency last_checked last_updated rate }}', ["prefer" => $aggregate]); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => [ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'rate', + ], + ], + ], + ]); + + Rate::query()->enabled()->updated()->preferred($aggregate)->get(["rate_currency", "updated_at", "rate_updated_at", "rate"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'rate' => $rate->rate, + ]); + }); + } + + /** + * Test the currency of the api + */ + public function test_filter_currency_works(): void + { + + $currency = Rate::query()->enabled()->updated()->first(['rate_currency'])->currency; + + $response = $this->graphQl(/** @lang GraphQL */ 'query ($currency: Currency!) { USD: rate(currency : $currency) { currency last_checked last_updated name rate url }}', ["currency" => $currency]); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => [ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + ], + ]); + + Rate::query()->enabled()->updated()->currency($currency)->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->rate_name, + 'rate' => $rate->rate, + 'url' => $rate->source_url, + ]); + }); + } + + /** + * Test the date of the api + */ + public function test_filter_date_works(): void + { + + $date = Carbon::now()->subDay()->getTimestamp(); + + $response = $this->graphQl(/** @lang GraphQL */ 'query ($date : Int!) { USD : rate(date: $date) { currency last_checked last_updated name rate url }}', ["date" => $date]); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => [ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + ], + ]); + + Rate::query()->enabled()->updated()->date($date)->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->rate_name, + 'rate' => $rate->rate, + 'url' => $rate->source_url, + ]); + }); + + } + + /** + * Test the info is returned in response + */ + public function test_info_is_included_in_response(): void + { + $response = $this->graphQl(/** @lang GraphQL */ 'query {USD : rate { currency last_checked last_updated name rate url }, info: info}'); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => [ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + 'info' + ], + ]); + + $response->assertJsonFragment([ + 'info' => Option::query()->firstWhere('key', 'notice')->value('value'), + ]); + } + + /** + * Test the cors support of the api + */ + public function test_cors_headers_are_set(): void + { + $response = $this->graphQl(/** @lang GraphQL */ 'query($cors : Boolean!) {USD : rate(cors : $cors) { currency last_checked last_updated name rate url }}', ["cors" => true]); + + $response->assertStatus(200); + $response->assertHeader('Access-Control-Allow-Origin', '*'); + } +} diff --git a/tests/Feature/ApiVersion0Test.php b/tests/Feature/ApiVersion0Test.php new file mode 100644 index 0000000..d83f107 --- /dev/null +++ b/tests/Feature/ApiVersion0Test.php @@ -0,0 +1,153 @@ +getJson('api'); + + $response->assertStatus(200); + $response->assertJsonStructure([ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ]); + + Rate::query()->enabled()->updated()->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->rate_name, + 'rate' => $rate->rate, + 'url' => $rate->source_url, + ]); + }); + + } + + /** + * Test the prefer aggregate of the api + */ + public function test_filter_prefer_aggregate_works(): void + { + $query = [ + 'prefer' => "MEDIAN", + ]; + + $response = $this->getJson('api?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertJsonStructure([ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'rate', + ], + ]); + + Rate::query()->enabled()->updated()->preferred($query["prefer"])->get(["rate_currency", "updated_at", "rate_updated_at", "rate"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'rate' => $rate->rate, + ]); + }); + } + + /** + * Test the currency of the api + */ + public function test_filter_currency_works(): void + { + $query = [ + 'currency' => Rate::query()->enabled()->updated()->first(['rate_currency'])->currency, + ]; + + $response = $this->getJson('api?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertJsonStructure([ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ] + ]); + + Rate::query()->enabled()->updated()->currency($query["currency"])->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->rate_name, + 'rate' => $rate->rate, + 'url' => $rate->source_url, + ]); + }); + + } + + /** + * Test the date of the api + */ + public function test_filter_date_works(): void + { + + $query = [ + 'date' => Carbon::now()->subDay()->getTimestamp(), + ]; + + $response = $this->getJson('api?' . Arr::query($query)); + + $response->assertStatus(200); + + $response->assertJsonStructure([ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ] + ]); + + Rate::query()->enabled()->updated()->date($query["date"])->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->rate_currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->rate_name, + 'rate' => $rate->rate, + 'url' => $rate->source_url, + ]); + }); + } +} diff --git a/tests/Feature/ApiVersion1Test.php b/tests/Feature/ApiVersion1Test.php new file mode 100644 index 0000000..2fbdc7d --- /dev/null +++ b/tests/Feature/ApiVersion1Test.php @@ -0,0 +1,229 @@ +getJson('api/v1'); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ] + ] + ]); + + Rate::query()->enabled()->updated()->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->name, + 'rate' => $rate->rate, + 'url' => $rate->url, + ]); + }); + + } + + /** + * Test the prefer aggregate of the api + */ + public function test_filter_prefer_aggregate_works(): void + { + + $query = [ + 'prefer' => "MEDIAN", + ]; + + $response = $this->getJson('api/v1?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'rate', + ], + ], + ]); + + Rate::query()->enabled()->updated()->preferred($query["prefer"])->get(["rate_currency", "updated_at", "rate_updated_at", "rate"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'rate' => $rate->rate, + ]); + }); + } + + /** + * Test the currency of the api + */ + public function test_filter_currency_works(): void + { + + $query = [ + 'currency' => Rate::query()->enabled()->updated()->first(['rate_currency'])->currency, + ]; + + $response = $this->getJson('api/v1?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + ]); + + Rate::query()->enabled()->updated()->currency($query["currency"])->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->name, + 'rate' => $rate->rate, + 'url' => $rate->url, + ]); + }); + } + + /** + * Test the date of the api + */ + public function test_filter_date_works(): void + { + + $query = [ + 'date' => Carbon::now()->subDay()->getTimestamp(), + ]; + + $response = $this->getJson('api/v1?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + ]); + + Rate::query()->enabled()->updated()->date($query["date"])->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->name, + 'rate' => $rate->rate, + 'url' => $rate->url, + ]); + }); + } + + /** + * Test the information removed of the api + */ + public function test_info_is_excluded_in_response(): void + { + $query = [ + 'info' => false, + ]; + + $response = $this->getJson('api/v1?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'USD' => [ + "*" => [ + 'currency', + 'last_checked', + 'last_updated', + 'name', + 'rate', + 'url' + ], + ], + ]); + + Rate::query()->enabled()->updated()->get(["rate_currency", "updated_at", "rate_updated_at", "rate_name", "rate", "source_url"])->each(function (Rate $rate) use ($response) { + $response->assertJsonFragment([ + 'currency' => $rate->currency, + 'last_checked' => $rate->last_checked, + 'last_updated' => $rate->last_updated, + 'name' => $rate->name, + 'rate' => $rate->rate, + 'url' => $rate->url, + ]); + }); + } + + /** + * Test the javascript callback of the api + */ + public function test_jsonp_callback(): void + { + $query = [ + 'callback' => 'test', + ]; + + $response = $this->getJson('api/v1?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'text/javascript; charset=UTF-8'); + + $response->assertSee('test('); + } + + /** + * Test the cors support of the api + */ + public function test_cors_headers_are_set(): void + { + $query = [ + 'cors' => true, + ]; + + $response = $this->getJson('api/v1?' . Arr::query($query)); + + $response->assertStatus(200); + $response->assertHeader('Access-Control-Allow-Origin', '*'); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php deleted file mode 100644 index 8364a84..0000000 --- a/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/tests/Feature/RateScopeTest.php b/tests/Feature/RateScopeTest.php new file mode 100644 index 0000000..237a865 --- /dev/null +++ b/tests/Feature/RateScopeTest.php @@ -0,0 +1,117 @@ +enabled()->get(["enabled"]); + + $rates->each(function (Rate $rate) { + $this->assertTrue($rate->enabled); + }); + } + + /** + * Test scope updated. + */ + public function test_scope_updated(): void + { + $rates = Rate::query()->enabled()->updated()->get(["status", "updated_at"]); + + $rates->each(function (Rate $rate) { + if ($rate->status === false) { + $this->assertGreaterThanOrEqual(Carbon::now()->startOfHour()->subWeek()->timestamp, $rate->updated_at->timestamp); + } + }); + } + + /** + * Test scope search. + */ + public function test_scope_search(): void + { + + $search = Rate::query()->inRandomOrder()->first(["rate_name"])->rate_name; + + $rates = Rate::query()->search($search)->get(["rate_name"]); + + $rates->each(function (Rate $rate) use ($search) { + $this->assertStringContainsString($search, $rate->rate_name); + }); + } + + /** + * Test scope date. + */ + public function test_scope_date(): void + { + + $date = Rate::query()->oldest("updated_at")->first(["updated_at"])->last_checked; + + $rates = Rate::query()->date($date)->get(["updated_at"]); + + $rates->each(function (Rate $rate) use ($date) { + $this->assertGreaterThanOrEqual($date, $rate->last_checked); + }); + } + + /** + * Test scope currency. + */ + public function test_scope_currency(): void + { + $currency = Rate::query()->inRandomOrder()->first()->rate_currency; + + $rates = Rate::query()->currency($currency)->get(); + + $rates->each(function (Rate $rate) use ($currency) { + $this->assertEquals($currency, $rate->rate_currency); + }); + } + + public function test_scope_preferred(): void + { + + $prefer = ["MIN", "MAX", "MEAN", "MODE", "MEDIAN", "RANDOM"]; + + foreach ($prefer as $aggregate) { + $rates = Rate::query()->preferred($aggregate)->get(["rate", "rate_currency"]); + + $rates->each(function (Rate $rate) use ($aggregate) { + switch ($aggregate) { + case "MIN": + $this->assertEquals($rate->rate, Rate::query()->currency($rate->rate_currency)->min("rate")); + break; + case "MAX": + $this->assertEquals($rate->rate, Rate::query()->currency($rate->rate_currency)->max("rate")); + break; + case "MEAN": + $this->assertEquals($rate->rate, Rate::query()->currency($rate->rate_currency)->avg("rate")); + break; + case "MODE": + $this->assertContains(intval($rate->rate), Rate::query()->preferred($aggregate)->currency($rate->rate_currency)->get(["rate"])->mode("rate")); + break; + case "MEDIAN": + $this->assertEquals(floor($rate->rate), floor(Rate::query()->preferred($aggregate)->currency($rate->rate_currency)->get(["rate"])->median("rate"))); + break; + case "RANDOM": + $this->assertContains($rate->rate, Rate::query()->currency($rate->rate_currency)->get(["rate"])->pluck("rate")); + break; + } + }); + } + + } +} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 5773b0c..99bb656 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -9,7 +9,7 @@ class ExampleTest extends TestCase /** * A basic test example. */ - public function test_that_true_is_true(): void + public function test_that_tests_work(): void { $this->assertTrue(true); }