aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Timeline/ClientApp/.vscode/launch.json11
-rw-r--r--Timeline/ClientApp/angular.json5
-rw-r--r--Timeline/ClientApp/package.json1
-rw-r--r--Timeline/ClientApp/src/app/home/home.component.spec.ts5
-rw-r--r--Timeline/ClientApp/src/app/home/home.component.ts13
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts46
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts71
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts40
-rw-r--r--Timeline/ClientApp/src/karma.conf.js1
-rw-r--r--Timeline/ClientApp/src/test.ts2
-rw-r--r--Timeline/Configs/TodoListConfig.cs12
-rw-r--r--Timeline/Configs/TodoPageConfig.cs21
-rw-r--r--Timeline/Controllers/TodoListController.cs11
-rw-r--r--Timeline/Startup.cs2
-rw-r--r--Timeline/appsettings.json6
-rw-r--r--azure-pipelines.yml20
16 files changed, 202 insertions, 65 deletions
diff --git a/Timeline/ClientApp/.vscode/launch.json b/Timeline/ClientApp/.vscode/launch.json
index 5b749430..96a3c552 100644
--- a/Timeline/ClientApp/.vscode/launch.json
+++ b/Timeline/ClientApp/.vscode/launch.json
@@ -7,9 +7,16 @@
{
"type": "chrome",
"request": "launch",
- "name": "Launch Chrome against localhost",
+ "name": "Launch app",
"url": "https://localhost:5001",
"webRoot": "${workspaceFolder}"
- }
+ },
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Launch test",
+ "url": "http://localhost:9876",
+ "webRoot": "${workspaceFolder}"
+ }
]
}
diff --git a/Timeline/ClientApp/angular.json b/Timeline/ClientApp/angular.json
index bf8a95ed..9a696714 100644
--- a/Timeline/ClientApp/angular.json
+++ b/Timeline/ClientApp/angular.json
@@ -1,6 +1,9 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
+ "cli": {
+ "packageManager": "yarn"
+ },
"newProjectRoot": "projects",
"projects": {
"Timeline": {
@@ -73,7 +76,7 @@
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
- "styles.css"
+ "src/styles.css"
],
"scripts": [],
"assets": [
diff --git a/Timeline/ClientApp/package.json b/Timeline/ClientApp/package.json
index 205874e5..9d948004 100644
--- a/Timeline/ClientApp/package.json
+++ b/Timeline/ClientApp/package.json
@@ -46,6 +46,7 @@
"karma-coverage-istanbul-reporter": "^2.0.4",
"karma-jasmine": "^2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
+ "karma-junit-reporter": "^1.2.0",
"tslint": "^5.12.1",
"typescript": "~3.2.4"
},
diff --git a/Timeline/ClientApp/src/app/home/home.component.spec.ts b/Timeline/ClientApp/src/app/home/home.component.spec.ts
index 490e81bd..74bedd08 100644
--- a/Timeline/ClientApp/src/app/home/home.component.spec.ts
+++ b/Timeline/ClientApp/src/app/home/home.component.spec.ts
@@ -2,15 +2,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
+
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ HomeComponent ]
+ declarations: [HomeComponent],
})
- .compileComponents();
+ .compileComponents();
}));
beforeEach(() => {
diff --git a/Timeline/ClientApp/src/app/home/home.component.ts b/Timeline/ClientApp/src/app/home/home.component.ts
index 873aee7f..2b16eef7 100644
--- a/Timeline/ClientApp/src/app/home/home.component.ts
+++ b/Timeline/ClientApp/src/app/home/home.component.ts
@@ -1,10 +1,4 @@
import { Component, OnInit } from '@angular/core';
-import { HttpClient } from '@angular/common/http';
-
-class LoginInfo {
- username = '';
- password = '';
-}
@Component({
selector: 'app-home',
@@ -13,15 +7,8 @@ class LoginInfo {
})
export class HomeComponent implements OnInit {
- loginInfo = new LoginInfo();
message = '';
- constructor(/* private http: HttpClient */) { }
-
ngOnInit() {
}
-
- tryLogin() {
- alert('Not implemented!!!');
- }
}
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts
index a74ce3e6..7fc4164d 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts
@@ -1,16 +1,45 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
+import { async, ComponentFixture, TestBed, tick } from '@angular/core/testing';
+
+import { defer, Observable } from 'rxjs';
import { TodoListPageComponent } from './todo-list-page.component';
+import { TodoListService, WorkItem } from './todo-list.service';
+import { By } from '@angular/platform-browser';
+
+@Component({
+ selector: 'mat-progress-bar',
+ template: ''
+})
+class MatProgressBarStubComponent {
+
+}
+
+function asyncData<T>(data: T): Observable<T> {
+ return defer(() => Promise.resolve(data));
+}
describe('TodoListPageComponent', () => {
let component: TodoListPageComponent;
let fixture: ComponentFixture<TodoListPageComponent>;
beforeEach(async(() => {
+ const todoListService: jasmine.SpyObj<TodoListService> = jasmine.createSpyObj('TodoListService', ['getWorkItemList']);
+
+ todoListService.getWorkItemList.and.returnValue(asyncData(<WorkItem[]>[{
+ id: 0, title: 'Test title 1', closed: true
+ }, {
+ id: 1, title: 'Test title 2', closed: false
+ }]));
+
TestBed.configureTestingModule({
- declarations: [ TodoListPageComponent ]
+ declarations: [TodoListPageComponent, MatProgressBarStubComponent],
+ providers: [
+ { provide: TodoListService, useValue: todoListService }
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
})
- .compileComponents();
+ .compileComponents();
}));
beforeEach(() => {
@@ -22,4 +51,15 @@ describe('TodoListPageComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ it('should show progress bar during loading', () => {
+ expect(fixture.debugElement.query(By.css('mat-progress-bar'))).toBeTruthy();
+ });
+
+ it('should hide progress bar after loading', async(() => {
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('mat-progress-bar'))).toBeFalsy();
+ });
+ }));
});
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts
index 529ba8cc..7e88ca52 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts
@@ -1,12 +1,79 @@
import { TestBed } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { TodoListService, WorkItem, AzureDevOpsAccessInfo, WiqlResult, WiqlWorkItemResult, WorkItemResult } from './todo-list.service';
-import { TodoListService } from './todo-list.service';
describe('TodoListServiceService', () => {
- beforeEach(() => TestBed.configureTestingModule({}));
+ beforeEach(() => TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule]
+ }));
it('should be created', () => {
+
+ const service: TodoListService = TestBed.get(TodoListService);
+ expect(service).toBeTruthy();
+ });
+
+ it('should work well', () => {
const service: TodoListService = TestBed.get(TodoListService);
expect(service).toBeTruthy();
+
+ const mockWorkItems: WorkItem[] = [{
+ id: 0,
+ title: 'Test work item 1',
+ closed: true
+ }, {
+ id: 1,
+ title: 'Test work item 2',
+ closed: false
+ }];
+
+ service.getWorkItemList().subscribe(data => {
+ expect(data).toEqual(mockWorkItems);
+ });
+
+ const httpController: HttpTestingController = TestBed.get(HttpTestingController);
+
+ const mockAccessInfo: AzureDevOpsAccessInfo = {
+ username: 'testusername',
+ personalAccessToken: 'testtoken',
+ organization: 'testorganization',
+ project: 'testproject'
+ };
+
+ httpController.expectOne('/api/TodoPage/AzureDevOpsAccessInfo').flush(mockAccessInfo);
+
+ const mockWiqlWorkItems: WiqlWorkItemResult[] = [{
+ id: 0,
+ url: `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/workItems/0`
+ }, {
+ id: 1,
+ url: `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/workItems/1`
+ }];
+
+ const authorizationHeader = 'Basic ' + btoa(mockAccessInfo.username + ':' + mockAccessInfo.personalAccessToken);
+
+ httpController.expectOne(req =>
+ req.url === `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/wiql?api-version=5.0` &&
+ req.headers.get('Authorization') === authorizationHeader
+ ).flush(<WiqlResult>{ workItems: mockWiqlWorkItems });
+
+ function mapWorkItemToResult(workItem: WorkItem): WorkItemResult {
+ return {
+ id: workItem.id,
+ fields: {
+ [TodoListService.titleFieldName]: workItem.title,
+ [TodoListService.stateFieldName]: (workItem.closed ? 'Closed' : 'Active')
+ }
+ };
+ }
+
+ for (let i = 0; i < mockWorkItems.length; i++) {
+ httpController.expectOne(req =>
+ req.url === mockWiqlWorkItems[i].url &&
+ req.headers.get('Authorization') === authorizationHeader
+ ).flush(mapWorkItemToResult(mockWorkItems[i]));
+ }
});
});
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts
index 0b54653b..619e9a6b 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts
@@ -3,20 +3,28 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { switchMap, concatMap, map, toArray } from 'rxjs/operators';
-interface WiqlWorkItemResult {
+export interface AzureDevOpsAccessInfo {
+ username: string;
+ personalAccessToken: string;
+ organization: string;
+ project: string;
+}
+
+export interface WiqlWorkItemResult {
id: number;
url: string;
}
-interface WiqlResult {
+export interface WiqlResult {
workItems: WiqlWorkItemResult[];
}
-interface WorkItemResult {
+export interface WorkItemResult {
id: number;
fields: { [name: string]: any };
}
+
export interface WorkItem {
id: number;
title: string;
@@ -28,41 +36,33 @@ export interface WorkItem {
})
export class TodoListService {
- private username = 'crupest';
- private organization = 'crupest-web';
- private project = 'Timeline';
- private titleFieldName = 'System.Title';
- private stateFieldName = 'System.State';
+ public static titleFieldName = 'System.Title';
+ public static stateFieldName = 'System.State';
constructor(private client: HttpClient) { }
- private getAzureDevOpsPat(): Observable<string> {
- return this.client.get('/api/TodoList/AzureDevOpsPat', {
- headers: {
- 'Accept': 'text/plain'
- },
- responseType: 'text'
- });
+ private getAzureDevOpsPat(): Observable<AzureDevOpsAccessInfo> {
+ return this.client.get<AzureDevOpsAccessInfo>('/api/TodoPage/AzureDevOpsAccessInfo');
}
getWorkItemList(): Observable<WorkItem[]> {
return this.getAzureDevOpsPat().pipe(
switchMap(
- pat => {
+ accessInfo => {
const headers = new HttpHeaders({
'Accept': 'application/json',
- 'Authorization': `Basic ${btoa(this.username + ':' + pat)}`
+ 'Authorization': `Basic ${btoa(accessInfo.username + ':' + accessInfo.personalAccessToken)}`
});
return this.client.post<WiqlResult>(
- `https://dev.azure.com/${this.organization}/${this.project}/_apis/wit/wiql?api-version=5.0`, {
+ `https://dev.azure.com/${accessInfo.organization}/${accessInfo.project}/_apis/wit/wiql?api-version=5.0`, {
query: 'SELECT [System.Id] FROM workitems WHERE [System.TeamProject] = @project'
}, { headers: headers }).pipe(
switchMap(result => result.workItems),
concatMap(result => this.client.get<WorkItemResult>(result.url, { headers: headers })),
map(result => <WorkItem>{
id: result.id,
- title: <string>result.fields[this.titleFieldName],
- closed: ((<string>result.fields[this.stateFieldName]).toLowerCase() === 'closed')
+ title: <string>result.fields[TodoListService.titleFieldName],
+ closed: ((<string>result.fields[TodoListService.stateFieldName]).toLowerCase() === 'closed')
}),
toArray()
);
diff --git a/Timeline/ClientApp/src/karma.conf.js b/Timeline/ClientApp/src/karma.conf.js
index 4a9730b9..775e624c 100644
--- a/Timeline/ClientApp/src/karma.conf.js
+++ b/Timeline/ClientApp/src/karma.conf.js
@@ -10,6 +10,7 @@ module.exports = function (config) {
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
+ require('karma-junit-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
diff --git a/Timeline/ClientApp/src/test.ts b/Timeline/ClientApp/src/test.ts
index 16317897..2513deed 100644
--- a/Timeline/ClientApp/src/test.ts
+++ b/Timeline/ClientApp/src/test.ts
@@ -1,6 +1,8 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+import 'zone.js/dist/zone-patch-rxjs-fake-async';
import 'zone.js/dist/zone-testing';
+
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
diff --git a/Timeline/Configs/TodoListConfig.cs b/Timeline/Configs/TodoListConfig.cs
deleted file mode 100644
index a69e8d03..00000000
--- a/Timeline/Configs/TodoListConfig.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace Timeline.Configs
-{
- public class TodoListConfig
- {
- public string AzureDevOpsPat { get; set; }
- }
-}
diff --git a/Timeline/Configs/TodoPageConfig.cs b/Timeline/Configs/TodoPageConfig.cs
new file mode 100644
index 00000000..d49e9dc3
--- /dev/null
+++ b/Timeline/Configs/TodoPageConfig.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Timeline.Configs
+{
+ public class AzureDevOpsAccessInfo
+ {
+ public string Username { get; set; }
+ public string PersonalAccessToken { get; set; }
+ public string Organization { get; set; }
+ public string Project { get; set; }
+
+ }
+
+ public class TodoPageConfig
+ {
+ public AzureDevOpsAccessInfo AzureDevOpsAccessInfo { get; set; }
+ }
+}
diff --git a/Timeline/Controllers/TodoListController.cs b/Timeline/Controllers/TodoListController.cs
index b773ed2e..257c1d85 100644
--- a/Timeline/Controllers/TodoListController.cs
+++ b/Timeline/Controllers/TodoListController.cs
@@ -10,21 +10,20 @@ using Timeline.Configs;
namespace Timeline.Controllers
{
[Route("api/[controller]")]
- public class TodoListController : Controller
+ public class TodoPageController : Controller
{
- private readonly IOptionsMonitor<TodoListConfig> _config;
+ private readonly IOptionsMonitor<TodoPageConfig> _config;
- public TodoListController(IOptionsMonitor<TodoListConfig> config)
+ public TodoPageController(IOptionsMonitor<TodoPageConfig> config)
{
_config = config;
}
[HttpGet("[action]")]
[AllowAnonymous]
- [Produces("text/plain")]
- public ActionResult<string> AzureDevOpsPat()
+ public ActionResult<AzureDevOpsAccessInfo> AzureDevOpsAccessInfo()
{
- return Ok(_config.CurrentValue.AzureDevOpsPat);
+ return Ok(_config.CurrentValue.AzureDevOpsAccessInfo);
}
}
}
diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs
index a6bde7fd..e06a98d5 100644
--- a/Timeline/Startup.cs
+++ b/Timeline/Startup.cs
@@ -41,7 +41,7 @@ namespace Timeline
});
- services.Configure<TodoListConfig>(Configuration.GetSection("TodoListConfig"));
+ services.Configure<TodoPageConfig>(Configuration.GetSection("TodoPageConfig"));
services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
var jwtConfig = Configuration.GetSection("JwtConfig").Get<JwtConfig>();
diff --git a/Timeline/appsettings.json b/Timeline/appsettings.json
index a914603c..8dc28bc8 100644
--- a/Timeline/appsettings.json
+++ b/Timeline/appsettings.json
@@ -7,5 +7,11 @@
"JwtConfig": {
"Issuer": "crupest.xyz",
"Audience": "crupest.xyz"
+ },
+ "TodoPageConfig": {
+ "AzureDevOpsAccessInfo": {
+ "Organization": "crupest-web",
+ "Project": "Timeline"
+ }
}
}
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index b67afdc2..4bbf12af 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -3,9 +3,6 @@
# Add steps that run tests, create a NuGet package, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
-trigger:
-- master
-
pool:
vmImage: 'Ubuntu-16.04'
@@ -15,8 +12,10 @@ variables:
steps:
- script: dotnet build --configuration $(buildConfiguration) --configfile nuget.config
+ displayName: Dotnet Build
- script: dotnet test Timeline.Tests --configuration $(buildConfiguration) --logger trx
+ displayName: Dotnet Test
- task: PublishTestResults@2
condition: succeededOrFailed()
@@ -24,7 +23,22 @@ steps:
testRunner: VSTest
testResultsFiles: '**/*.trx'
+- script: yarn install
+ workingDirectory: Timeline/ClientApp
+ displayName: Yarn Install
+
+- script: yarn run test --no-watch --browsers=ChromeHeadless --reporters junit
+ workingDirectory: Timeline/ClientApp
+ displayName: Angular Test
+
+- task: PublishTestResults@2
+ condition: succeededOrFailed()
+ inputs:
+ testRunner: JUnit
+ testResultsFiles: '**/TESTS-*.xml'
+
- script: dotnet publish Timeline/Timeline.csproj --configuration $(buildConfiguration) --output ./publish/
+ displayName: Dotnet Publish
- task: PublishPipelineArtifact@0
inputs: