From c871c32383ed9468059943703bc354af310319e3 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 11:24:37 +0000 Subject: [PATCH 1/9] connected website to api --- .../app/services/paymentsense-coding-challenge-api.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paymentsense-coding-challenge-website/src/app/services/paymentsense-coding-challenge-api.service.ts b/paymentsense-coding-challenge-website/src/app/services/paymentsense-coding-challenge-api.service.ts index 1dddc8c..66d7621 100644 --- a/paymentsense-coding-challenge-website/src/app/services/paymentsense-coding-challenge-api.service.ts +++ b/paymentsense-coding-challenge-website/src/app/services/paymentsense-coding-challenge-api.service.ts @@ -9,6 +9,6 @@ export class PaymentsenseCodingChallengeApiService { constructor(private httpClient: HttpClient) {} public getHealth(): Observable { - return this.httpClient.get('https://localhost:44341/health', { responseType: 'text' }); + return this.httpClient.get('https://localhost:44339/health', { responseType: 'text' }); } } From d2df56ef146a0dc1039909748fe98a696cdc05da Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 13:23:50 +0000 Subject: [PATCH 2/9] added country list endpoint. task-2 --- ...mentsenseCodingChallengeControllerTests.cs | 9 +++- ...entsense.Coding.Challenge.Api.Tests.csproj | 1 + .../PaymentsenseCodingChallengeController.cs | 19 +++++++- .../Interfaces/ICountryDataProvider.cs | 10 +++++ .../Models/Country.cs | 13 ++++++ .../Paymentsense.Coding.Challenge.Api.csproj | 3 +- .../Services/CountryDataProvider.cs | 43 +++++++++++++++++++ .../Startup.cs | 6 +++ .../appsettings.json | 3 +- 9 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Interfaces/ICountryDataProvider.cs create mode 100644 paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs create mode 100644 paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Services/CountryDataProvider.cs diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs index 9f33c41..f8a64a6 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs @@ -1,7 +1,11 @@ -using FluentAssertions; +using System.Net.Http; +using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Moq; using Paymentsense.Coding.Challenge.Api.Controllers; +using Paymentsense.Coding.Challenge.Api.Interfaces; +using Paymentsense.Coding.Challenge.Api.Services; using Xunit; namespace Paymentsense.Coding.Challenge.Api.Tests.Controllers @@ -11,7 +15,8 @@ public class PaymentsenseCodingChallengeControllerTests [Fact] public void Get_OnInvoke_ReturnsExpectedMessage() { - var controller = new PaymentsenseCodingChallengeController(); + var countryRepoServiceMock = new Mock(); + var controller = new PaymentsenseCodingChallengeController(countryRepoServiceMock.Object); var result = controller.Get().Result as OkObjectResult; diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Paymentsense.Coding.Challenge.Api.Tests.csproj b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Paymentsense.Coding.Challenge.Api.Tests.csproj index ba38576..137b9e7 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Paymentsense.Coding.Challenge.Api.Tests.csproj +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Paymentsense.Coding.Challenge.Api.Tests.csproj @@ -10,6 +10,7 @@ + all diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs index d7ced3c..9e29716 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Paymentsense.Coding.Challenge.Api.Interfaces; namespace Paymentsense.Coding.Challenge.Api.Controllers { @@ -6,10 +8,25 @@ namespace Paymentsense.Coding.Challenge.Api.Controllers [Route("[controller]")] public class PaymentsenseCodingChallengeController : ControllerBase { + public ICountryDataProvider _countryRepositoryService { get; } + + public PaymentsenseCodingChallengeController(ICountryDataProvider countryRepositoryService) + { + _countryRepositoryService = countryRepositoryService; + } + [HttpGet] public ActionResult Get() { return Ok("Paymentsense Coding Challenge!"); } + + [Route("countries/list")] + [HttpGet] + public async Task CountryList() + { + var list = await _countryRepositoryService.GetCountryList(); + return Ok(list); + } } } diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Interfaces/ICountryDataProvider.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Interfaces/ICountryDataProvider.cs new file mode 100644 index 0000000..e0dc54d --- /dev/null +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Interfaces/ICountryDataProvider.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Paymentsense.Coding.Challenge.Api.Models; + +namespace Paymentsense.Coding.Challenge.Api.Interfaces +{ + public interface ICountryDataProvider + { + Task GetCountryList(); + } +} diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs new file mode 100644 index 0000000..a4a120d --- /dev/null +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Paymentsense.Coding.Challenge.Api.Models +{ + public class Country + { + public string Name { get; set; } + public string Flag { get; set; } + } +} diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Paymentsense.Coding.Challenge.Api.csproj b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Paymentsense.Coding.Challenge.Api.csproj index 1d82fb7..bdce095 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Paymentsense.Coding.Challenge.Api.csproj +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Paymentsense.Coding.Challenge.Api.csproj @@ -6,8 +6,7 @@ - - + diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Services/CountryDataProvider.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Services/CountryDataProvider.cs new file mode 100644 index 0000000..d9d6817 --- /dev/null +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Services/CountryDataProvider.cs @@ -0,0 +1,43 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Paymentsense.Coding.Challenge.Api.Interfaces; +using Paymentsense.Coding.Challenge.Api.Models; + +namespace Paymentsense.Coding.Challenge.Api.Services +{ + public class CountryDataProvider : ICountryDataProvider + { + private readonly IHttpClientFactory _clientFactory; + private readonly IConfiguration _configuration; + + public CountryDataProvider(IHttpClientFactory clientFactory, IConfiguration configuration) + { + _clientFactory = clientFactory; + _configuration = configuration; + } + + public async Task GetCountryList() + { + // Get country data source url from config + var sourceUrl = _configuration.GetValue("DataSource"); + if (string.IsNullOrEmpty(sourceUrl)) + { + throw new ApplicationException("Country data source url missing in configuration file"); + } + + // Make call to fetch country list + var client = _clientFactory.CreateClient(); + var response = await client.GetAsync(sourceUrl); + if (response != null && response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content); + } + + return null; + } + } +} diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs index 623b8b2..e7160f5 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Paymentsense.Coding.Challenge.Api.Interfaces; +using Paymentsense.Coding.Challenge.Api.Services; namespace Paymentsense.Coding.Challenge.Api { @@ -29,6 +31,10 @@ public void ConfigureServices(IServiceCollection services) .AllowAnyHeader(); }); }); + + services.AddScoped(); + services.AddHttpClient(); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/appsettings.json b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/appsettings.json index d9d9a9b..00d6432 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/appsettings.json +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/appsettings.json @@ -6,5 +6,6 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "DataSource": "https://restcountries.eu/rest/v2/all" } From 18eb521ab09c1510b97aa19fdb96e93de46824b7 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 13:24:58 +0000 Subject: [PATCH 3/9] listed country name and flag on UI. task-2 --- .../src/app/app.component.html | 24 ++++++++++-- .../src/app/app.module.ts | 33 +++++++++-------- .../country-list/country-list.component.html | 36 ++++++++++++++++++ .../country-list/country-list.component.scss | 5 +++ .../country-list.component.spec.ts | 25 +++++++++++++ .../country-list/country-list.component.ts | 37 +++++++++++++++++++ .../src/app/models/country.ts | 4 ++ .../services/country-data-provider.service.ts | 18 +++++++++ .../src/app/services/index.ts | 3 +- 9 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html create mode 100644 paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss create mode 100644 paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts create mode 100644 paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts create mode 100644 paymentsense-coding-challenge-website/src/app/models/country.ts create mode 100644 paymentsense-coding-challenge-website/src/app/services/country-data-provider.service.ts diff --git a/paymentsense-coding-challenge-website/src/app/app.component.html b/paymentsense-coding-challenge-website/src/app/app.component.html index 9201572..b5926f9 100644 --- a/paymentsense-coding-challenge-website/src/app/app.component.html +++ b/paymentsense-coding-challenge-website/src/app/app.component.html @@ -1,11 +1,27 @@ -
- Paymentsense Logo +
+ Paymentsense Logo

{{ title }}

-

... Paymentsense Coding Challenge API is ...

+

+ ... Paymentsense Coding Challenge API is + + ... +

+ +
+
diff --git a/paymentsense-coding-challenge-website/src/app/app.module.ts b/paymentsense-coding-challenge-website/src/app/app.module.ts index 77745c5..58c0eb9 100644 --- a/paymentsense-coding-challenge-website/src/app/app.module.ts +++ b/paymentsense-coding-challenge-website/src/app/app.module.ts @@ -1,25 +1,28 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule } from "@angular/core"; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { PaymentsenseCodingChallengeApiService } from './services'; -import { HttpClientModule } from '@angular/common/http'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { AppRoutingModule } from "./app-routing.module"; +import { AppComponent } from "./app.component"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { PaymentsenseCodingChallengeApiService } from "./services"; +import { HttpClientModule } from "@angular/common/http"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { CountryListComponent } from "./country-list/country-list.component"; +import { CountryDataProviderService } from "./services/country-data-provider.service"; @NgModule({ - declarations: [ - AppComponent - ], + declarations: [AppComponent, CountryListComponent], imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, HttpClientModule, - FontAwesomeModule + FontAwesomeModule, + ], + providers: [ + PaymentsenseCodingChallengeApiService, + CountryDataProviderService, ], - providers: [PaymentsenseCodingChallengeApiService], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html new file mode 100644 index 0000000..d8204dd --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html @@ -0,0 +1,36 @@ +
+
+

No country found

+
+
+
+
+ + + + + + + + + + + + + +
+ Flag + + Country +
+ logo + + {{ country.name }} +
+
+
diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss new file mode 100644 index 0000000..72f4ace --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss @@ -0,0 +1,5 @@ +.container { + text-align: center; + border: 1px solid green; + padding: 5px; +} diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts new file mode 100644 index 0000000..ec93099 --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CountryListComponent } from './country-list.component'; + +describe('CountryListComponent', () => { + let component: CountryListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CountryListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CountryListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts new file mode 100644 index 0000000..31cdf92 --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts @@ -0,0 +1,37 @@ +import { CountryDataProviderService } from "./../services/country-data-provider.service"; +import { Component, OnInit } from "@angular/core"; +import { Country } from "../models/country"; +import { Subject } from "rxjs"; +import { takeUntil, tap, catchError } from "rxjs/operators"; + +@Component({ + selector: "country-list", + templateUrl: "./country-list.component.html", + styleUrls: ["./country-list.component.scss"], +}) +export class CountryListComponent implements OnInit { + public countryList: Country[] = []; + private unsubscribe$ = new Subject(); + + constructor(private countryDataProviderService: CountryDataProviderService) {} + + ngOnInit(): void { + this.getCountryList(); + } + + getCountryList() { + this.countryDataProviderService + .getCountryList() + .pipe( + takeUntil(this.unsubscribe$), + tap((data: Country[]) => { + this.countryList = data; + }), + catchError((error: any) => { + console.error(error); + return null; + }) + ) + .subscribe(); + } +} diff --git a/paymentsense-coding-challenge-website/src/app/models/country.ts b/paymentsense-coding-challenge-website/src/app/models/country.ts new file mode 100644 index 0000000..cd33d47 --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/models/country.ts @@ -0,0 +1,4 @@ +export class Country { + name: string; + flag: string; +} diff --git a/paymentsense-coding-challenge-website/src/app/services/country-data-provider.service.ts b/paymentsense-coding-challenge-website/src/app/services/country-data-provider.service.ts new file mode 100644 index 0000000..a40b895 --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/services/country-data-provider.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { HttpClient } from "@angular/common/http"; +import { map } from "rxjs/operators"; +import { Country } from "../models/country"; + +@Injectable({ + providedIn: "root", +}) +export class CountryDataProviderService { + constructor(private httpClient: HttpClient) {} + + public getCountryList(): Observable { + return this.httpClient + .get("https://localhost:44339/PaymentsenseCodingChallenge/countries/list") + .pipe(map((data: any) => data)); + } +} diff --git a/paymentsense-coding-challenge-website/src/app/services/index.ts b/paymentsense-coding-challenge-website/src/app/services/index.ts index d1f65b8..06b9c7a 100644 --- a/paymentsense-coding-challenge-website/src/app/services/index.ts +++ b/paymentsense-coding-challenge-website/src/app/services/index.ts @@ -1 +1,2 @@ -export * from './paymentsense-coding-challenge-api.service'; +export * from "./paymentsense-coding-challenge-api.service"; +export * from "./country-data-provider.service"; From c82dffd468e8b3498e3c9e6cd8d5c3df1e6cb187 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 20:04:05 +0000 Subject: [PATCH 4/9] added mat-table to list countries. task-2 --- .../src/app/app.module.ts | 2 + .../country-list/country-list.component.html | 55 +++++++++---------- .../country-list/country-list.component.scss | 6 +- .../country-list/country-list.component.ts | 8 ++- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/paymentsense-coding-challenge-website/src/app/app.module.ts b/paymentsense-coding-challenge-website/src/app/app.module.ts index 58c0eb9..2de569e 100644 --- a/paymentsense-coding-challenge-website/src/app/app.module.ts +++ b/paymentsense-coding-challenge-website/src/app/app.module.ts @@ -9,6 +9,7 @@ import { HttpClientModule } from "@angular/common/http"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { CountryListComponent } from "./country-list/country-list.component"; import { CountryDataProviderService } from "./services/country-data-provider.service"; +import { MatTableModule } from "@angular/material/table"; @NgModule({ declarations: [AppComponent, CountryListComponent], @@ -18,6 +19,7 @@ import { CountryDataProviderService } from "./services/country-data-provider.ser BrowserAnimationsModule, HttpClientModule, FontAwesomeModule, + MatTableModule, ], providers: [ PaymentsenseCodingChallengeApiService, diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html index d8204dd..5aeda9d 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html @@ -1,36 +1,31 @@ -
+
-

No country found

+

Loading...

-
-
- - - - - - - - - - - - - +
+
+
+

No country found

+
+
+
+
- Flag - - Country -
- logo - - {{ country.name }} -
+ + + + + + + + + + + + +
Flag + flag + Name{{ element.name }}
diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss index 72f4ace..1922e7f 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.scss @@ -1,5 +1,3 @@ -.container { - text-align: center; - border: 1px solid green; - padding: 5px; +table { + width: 100%; } diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts index 31cdf92..ee1a7fb 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts @@ -2,7 +2,7 @@ import { CountryDataProviderService } from "./../services/country-data-provider. import { Component, OnInit } from "@angular/core"; import { Country } from "../models/country"; import { Subject } from "rxjs"; -import { takeUntil, tap, catchError } from "rxjs/operators"; +import { takeUntil, tap, catchError, finalize } from "rxjs/operators"; @Component({ selector: "country-list", @@ -11,6 +11,8 @@ import { takeUntil, tap, catchError } from "rxjs/operators"; }) export class CountryListComponent implements OnInit { public countryList: Country[] = []; + public columnsToDisplay: string[] = ["flag", "name"]; + public loading = false; private unsubscribe$ = new Subject(); constructor(private countryDataProviderService: CountryDataProviderService) {} @@ -20,6 +22,7 @@ export class CountryListComponent implements OnInit { } getCountryList() { + this.loading = true; this.countryDataProviderService .getCountryList() .pipe( @@ -30,6 +33,9 @@ export class CountryListComponent implements OnInit { catchError((error: any) => { console.error(error); return null; + }), + finalize(() => { + this.loading = false; }) ) .subscribe(); From 48c46d9cb61e4bbe8f914585d7d3f247a96c5122 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 20:38:03 +0000 Subject: [PATCH 5/9] cached response. task-3 --- .../Controllers/PaymentsenseCodingChallengeController.cs | 4 +++- .../Paymentsense.Coding.Challenge.Api/Startup.cs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs index 9e29716..033a2db 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Controllers/PaymentsenseCodingChallengeController.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Paymentsense.Coding.Challenge.Api.Interfaces; @@ -21,6 +22,7 @@ public ActionResult Get() return Ok("Paymentsense Coding Challenge!"); } + [ResponseCache(Duration = int.MaxValue)] [Route("countries/list")] [HttpGet] public async Task CountryList() diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs index e7160f5..0adb0bb 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Startup.cs @@ -32,9 +32,12 @@ public void ConfigureServices(IServiceCollection services) }); }); + // Register country data provider services.AddScoped(); services.AddHttpClient(); + // Enable response caching + services.AddResponseCaching(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -58,6 +61,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) endpoints.MapControllers(); endpoints.MapHealthChecks("/health"); }); + + // Activate response caching middleware + app.UseResponseCaching(); } } } From 119445013495b15d2981860c8c566e34cb3b10d6 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 21:17:55 +0000 Subject: [PATCH 6/9] added pagination. task-4 --- .../src/app/app.module.ts | 2 ++ .../app/country-list/country-list.component.html | 7 ++++++- .../app/country-list/country-list.component.ts | 15 ++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/paymentsense-coding-challenge-website/src/app/app.module.ts b/paymentsense-coding-challenge-website/src/app/app.module.ts index 2de569e..2b21184 100644 --- a/paymentsense-coding-challenge-website/src/app/app.module.ts +++ b/paymentsense-coding-challenge-website/src/app/app.module.ts @@ -10,6 +10,7 @@ import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { CountryListComponent } from "./country-list/country-list.component"; import { CountryDataProviderService } from "./services/country-data-provider.service"; import { MatTableModule } from "@angular/material/table"; +import { MatPaginatorModule } from "@angular/material/paginator"; @NgModule({ declarations: [AppComponent, CountryListComponent], @@ -20,6 +21,7 @@ import { MatTableModule } from "@angular/material/table"; HttpClientModule, FontAwesomeModule, MatTableModule, + MatPaginatorModule, ], providers: [ PaymentsenseCodingChallengeApiService, diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html index 5aeda9d..0e39726 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html @@ -9,7 +9,7 @@

No country found

-
+
@@ -27,5 +27,10 @@
+
diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts index ee1a7fb..be4e1de 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts @@ -1,8 +1,10 @@ import { CountryDataProviderService } from "./../services/country-data-provider.service"; -import { Component, OnInit } from "@angular/core"; +import { Component, OnInit, ViewChild } from "@angular/core"; import { Country } from "../models/country"; import { Subject } from "rxjs"; import { takeUntil, tap, catchError, finalize } from "rxjs/operators"; +import { MatPaginator } from "@angular/material/paginator"; +import { MatTableDataSource } from "@angular/material/table"; @Component({ selector: "country-list", @@ -10,10 +12,17 @@ import { takeUntil, tap, catchError, finalize } from "rxjs/operators"; styleUrls: ["./country-list.component.scss"], }) export class CountryListComponent implements OnInit { - public countryList: Country[] = []; + public countryList: any; public columnsToDisplay: string[] = ["flag", "name"]; public loading = false; private unsubscribe$ = new Subject(); + @ViewChild(MatPaginator, { static: false }) set matPaginator( + paginator: MatPaginator + ) { + if (this.countryList) { + this.countryList.paginator = paginator; + } + } constructor(private countryDataProviderService: CountryDataProviderService) {} @@ -28,7 +37,7 @@ export class CountryListComponent implements OnInit { .pipe( takeUntil(this.unsubscribe$), tap((data: Country[]) => { - this.countryList = data; + this.countryList = new MatTableDataSource(data); }), catchError((error: any) => { console.error(error); From 75598458b68c9c7746af6580b270d7acb6705161 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 21:45:19 +0000 Subject: [PATCH 7/9] added extra fields into models. task-5 --- .../Models/Country.cs | 25 ++++++++++++++++--- .../src/app/models/country.ts | 8 ++++++ .../src/app/models/currency.ts | 5 ++++ .../src/app/models/language.ts | 6 +++++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 paymentsense-coding-challenge-website/src/app/models/currency.ts create mode 100644 paymentsense-coding-challenge-website/src/app/models/language.ts diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs index a4a120d..32d4270 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api/Models/Country.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Paymentsense.Coding.Challenge.Api.Models { @@ -9,5 +6,25 @@ public class Country { public string Name { get; set; } public string Flag { get; set; } + public int Population { get; set; } + public List Timezones { get; set; } + public List Currencies { get; set; } + public List Languages { get; set; } + public string Capital { get; set; } + } + + public class Currency + { + public string Code { get; set; } + public string Name { get; set; } + public string Symbol { get; set; } + } + + public class Language + { + public string Iso639_1 { get; set; } + public string Iso639_2 { get; set; } + public string Name { get; set; } + public string NativeName { get; set; } } } diff --git a/paymentsense-coding-challenge-website/src/app/models/country.ts b/paymentsense-coding-challenge-website/src/app/models/country.ts index cd33d47..e27b232 100644 --- a/paymentsense-coding-challenge-website/src/app/models/country.ts +++ b/paymentsense-coding-challenge-website/src/app/models/country.ts @@ -1,4 +1,12 @@ +import { Currency } from "./currency"; +import { Language } from "./language"; + export class Country { name: string; flag: string; + population: number; + timezones: string[]; + currencies: Currency[]; + languages: Language[]; + capital: string; } diff --git a/paymentsense-coding-challenge-website/src/app/models/currency.ts b/paymentsense-coding-challenge-website/src/app/models/currency.ts new file mode 100644 index 0000000..16e6bca --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/models/currency.ts @@ -0,0 +1,5 @@ +export class Currency { + code: string; + name: string; + symbol: string; +} diff --git a/paymentsense-coding-challenge-website/src/app/models/language.ts b/paymentsense-coding-challenge-website/src/app/models/language.ts new file mode 100644 index 0000000..1933ede --- /dev/null +++ b/paymentsense-coding-challenge-website/src/app/models/language.ts @@ -0,0 +1,6 @@ +export class Language { + iso639_1: string; + iso639_2: string; + name: string; + nativeName: string; +} From adbbd7f6359d85dd86d201f0a96f90e8d6b07369 Mon Sep 17 00:00:00 2001 From: Ibrahim Oguz Date: Mon, 7 Dec 2020 22:32:43 +0000 Subject: [PATCH 8/9] expanded extra country fields on grid. task-5 --- .../country-list/country-list.component.html | 41 ++++++++++++++++++- .../country-list/country-list.component.scss | 21 ++++++++++ .../country-list/country-list.component.ts | 28 +++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html index 0e39726..f6361ed 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.html @@ -10,7 +10,7 @@
- +
@@ -25,7 +25,44 @@ - + + + + + + +
Flag{{ element.name }}
+
+
+ Population: {{ element.population }} + Timezones: {{ element.timezones?.join() }} + Currencies: + {{ getCurrencies(element.currencies) }} + Languages: + {{ getLanguages(element.languages) }} Capital: + {{ element.capital }} +
+
+
collapsed", + animate("225ms cubic-bezier(0.4, 0.0, 0.2, 1)") + ), + ]), + ], }) export class CountryListComponent implements OnInit { public countryList: any; @@ -23,6 +42,7 @@ export class CountryListComponent implements OnInit { this.countryList.paginator = paginator; } } + expandedElement: Country | null; constructor(private countryDataProviderService: CountryDataProviderService) {} @@ -49,4 +69,12 @@ export class CountryListComponent implements OnInit { ) .subscribe(); } + + getCurrencies(currencies: Currency[]): string { + return currencies.map((c) => c.name).join(","); + } + + getLanguages(languages: Language[]): string { + return languages.map((l) => l.name).join(","); + } } From 447ddc024de02275d5188948f3cb36322831d4f5 Mon Sep 17 00:00:00 2001 From: "ibrahim.oguz" Date: Tue, 8 Dec 2020 01:11:40 +0000 Subject: [PATCH 9/9] fixed and added unit tests --- ...mentsenseCodingChallengeControllerTests.cs | 57 ++++++++++++++++++- .../src/app/app.component.spec.ts | 44 ++++++++------ .../country-list.component.spec.ts | 26 ++++++--- .../country-list/country-list.component.ts | 4 +- 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs index f8a64a6..259e2c2 100644 --- a/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs +++ b/paymentsense-coding-challenge-api/Paymentsense.Coding.Challenge.Api.Tests/Controllers/PaymentsenseCodingChallengeControllerTests.cs @@ -1,8 +1,14 @@ -using System.Net.Http; +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Moq; +using Moq.Protected; using Paymentsense.Coding.Challenge.Api.Controllers; using Paymentsense.Coding.Challenge.Api.Interfaces; using Paymentsense.Coding.Challenge.Api.Services; @@ -23,5 +29,54 @@ public void Get_OnInvoke_ReturnsExpectedMessage() result.StatusCode.Should().Be(StatusCodes.Status200OK); result.Value.Should().Be("Paymentsense Coding Challenge!"); } + + [Fact] + public void CountryList_MissingDataSource_ReturnsErrorMessage() + { + // Mock configuration to return wrong config key + var configurationMock = new Mock(); + var configurationSection = new Mock(); + configurationSection.Setup(a => a.Value).Returns("testvalue"); + configurationMock.Setup(a => a.GetSection("TestValueKey")).Returns(configurationSection.Object); + + var httpClientFactoryMock = new Mock(); + var countryRepoService = new CountryDataProvider(httpClientFactoryMock.Object, configurationMock.Object); + var controller = new PaymentsenseCodingChallengeController(countryRepoService); + + Assert.ThrowsAsync(() => controller.CountryList()); + } + + [Fact] + public async Task CountryList_WrongDataSource_ReturnsNullMessage() + { + // Mock configuration to return wrong data source url + var configurationMock = new Mock(); + var configurationSection = new Mock(); + configurationSection.Setup(a => a.Value).Returns("test123"); + configurationMock.Setup(a => a.GetSection("DataSource")).Returns(configurationSection.Object); + + var httpClientFactoryMock = new Mock(); + var mockHttpMessageHandlerMock = new Mock(); + + mockHttpMessageHandlerMock.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.NotFound, + }); + + var client = new HttpClient(mockHttpMessageHandlerMock.Object) + { + BaseAddress = new Uri("https://restcountries.eu/rest/v2/all"), + }; + httpClientFactoryMock.Setup(_ => _.CreateClient(It.IsAny())).Returns(client); + + var countryRepoService = new CountryDataProvider(httpClientFactoryMock.Object, configurationMock.Object); + var controller = new PaymentsenseCodingChallengeController(countryRepoService); + + var result = await controller.CountryList() as ObjectResult; + Assert.Null(result.Value); + } } } diff --git a/paymentsense-coding-challenge-website/src/app/app.component.spec.ts b/paymentsense-coding-challenge-website/src/app/app.component.spec.ts index 1a932fc..1be9b82 100644 --- a/paymentsense-coding-challenge-website/src/app/app.component.spec.ts +++ b/paymentsense-coding-challenge-website/src/app/app.component.spec.ts @@ -1,27 +1,35 @@ -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; -import { PaymentsenseCodingChallengeApiService } from './services'; -import { MockPaymentsenseCodingChallengeApiService } from './testing/mock-paymentsense-coding-challenge-api.service'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { TestBed, async } from "@angular/core/testing"; +import { RouterTestingModule } from "@angular/router/testing"; +import { AppComponent } from "./app.component"; +import { PaymentsenseCodingChallengeApiService } from "./services"; +import { MockPaymentsenseCodingChallengeApiService } from "./testing/mock-paymentsense-coding-challenge-api.service"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { CountryListComponent } from "./country-list/country-list.component"; +import { MatTableModule } from "@angular/material/table"; +import { MatPaginatorModule } from "@angular/material/paginator"; +import { HttpClientModule } from "@angular/common/http"; -describe('AppComponent', () => { +describe("AppComponent", () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule, - FontAwesomeModule - ], - declarations: [ - AppComponent + FontAwesomeModule, + MatTableModule, + MatPaginatorModule, + HttpClientModule, ], + declarations: [AppComponent, CountryListComponent], providers: [ - { provide: PaymentsenseCodingChallengeApiService, useClass: MockPaymentsenseCodingChallengeApiService } - ] + { + provide: PaymentsenseCodingChallengeApiService, + useClass: MockPaymentsenseCodingChallengeApiService, + }, + ], }).compileComponents(); })); - it('should create the app', () => { + it("should create the app", () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); @@ -30,13 +38,15 @@ describe('AppComponent', () => { it(`should have as title 'Paymentsense Coding Challenge'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('Paymentsense Coding Challenge!'); + expect(app.title).toEqual("Paymentsense Coding Challenge!"); }); - it('should render title in a h1 tag', () => { + it("should render title in a h1 tag", () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Paymentsense Coding Challenge!'); + expect(compiled.querySelector("h1").textContent).toContain( + "Paymentsense Coding Challenge!" + ); }); }); diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts index ec93099..6bd374c 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.spec.ts @@ -1,16 +1,26 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { CountryListComponent } from "./country-list.component"; +import { RouterTestingModule } from "@angular/router/testing"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { MatTableModule } from "@angular/material/table"; +import { MatPaginatorModule } from "@angular/material/paginator"; +import { HttpClientModule } from "@angular/common/http"; -import { CountryListComponent } from './country-list.component'; - -describe('CountryListComponent', () => { +describe("CountryListComponent", () => { let component: CountryListComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ CountryListComponent ] - }) - .compileComponents(); + imports: [ + RouterTestingModule, + FontAwesomeModule, + MatTableModule, + MatPaginatorModule, + HttpClientModule, + ], + declarations: [CountryListComponent], + }).compileComponents(); })); beforeEach(() => { @@ -19,7 +29,7 @@ describe('CountryListComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts index c309a3d..105e0dd 100644 --- a/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts +++ b/paymentsense-coding-challenge-website/src/app/country-list/country-list.component.ts @@ -1,7 +1,7 @@ import { CountryDataProviderService } from "./../services/country-data-provider.service"; import { Component, OnInit, ViewChild } from "@angular/core"; import { Country } from "../models/country"; -import { Subject } from "rxjs"; +import { Subject, of } from "rxjs"; import { takeUntil, tap, catchError, finalize } from "rxjs/operators"; import { MatPaginator } from "@angular/material/paginator"; import { MatTableDataSource } from "@angular/material/table"; @@ -61,7 +61,7 @@ export class CountryListComponent implements OnInit { }), catchError((error: any) => { console.error(error); - return null; + return of(); }), finalize(() => { this.loading = false;