Skip to content

task#24 create a user service

bacn edited this page Sep 16, 2020 · 1 revision

Create a User Service

The goal for this task is creating a new service. The service shall provide a CRUD (create, read, update delete) functionality for users plus a function to get all users:

verb url data Remark
get /users array of user objects role: admin
get /users/:id user object role: admin or own user
get /users/:username user object role: admin or own user
delete /users/:id user object role: admin
post /users user object role: admin
put /users:id user object role: admin or own user

The mock server is checking the api calls based on the Bearer token. Each api call shall be working either as an;

  • admin role or for some verbs.
  • or as a user role for own user profile.

Domain Model of the Analysis

The following domain model has been discovered during the analysis of the auction case study:


domain-model.png


List of tasks

  • Create a folder model in the folder src/app/shared.
  • Create user class user.ts based on analysis of the case study in the folder src/app/shared/model.
  • Upgrade the mock server for the new verbs post and put.
  • Upgrade the unit test for the mock server for the new verbs post and put.
  • Create a user service UserService by creating the file user.service.ts in the folder src/app/shared/service which supports the CRUD-operations of the user object
  • Create a unit test for UserService.
  • Create a service CreateUserService by creating the file create-user.service.ts in the folder src/app/shared/helper to create the default test users admin and user
  • Create a unit test for CreateUserService.

Result

Verify the result by unit tests for the user service and the default user service.


Hints

Create user class based on analysis of the case study

The user class is using the fields which were discovered during the analysis of the auction case study.

  • Create a folder model in the folder src/app/shared.
  • Create a file user.ts in the folder src/app/shared/model.
export class User {
  id?: number;
  username: string;
  password: string;
  firstName: string;
  lastName: string;
  email?: string;
  thresholdOpenPayment?: number;
  locked?: boolean;
}

Upgrade the mock server for the new verbs post and put

Since we shall support the new verbs post and put for the user, we must upgrade our mock server by adding a new user and changing a user. Since we have already a register function for adding a user, we can use it the for post verb. For the put verb we shall write a new function changeUser().

    function changeUser() {
      const user = body;
      if (!user.id) user.id = idFromUrl();
      let users = JSON.parse(localStorage.getItem('users')) || [];
      users = users.filter(x => x.id === idFromUrl());
      if (users.length > 0) {
        // delete this user
        let users = JSON.parse(localStorage.getItem('users')) || [];
        users = users.filter(x => x.id !== idFromUrl());
        // add changed user
        users.push(user);
        localStorage.setItem('users', JSON.stringify(users));
        return ok(user);
      } else {
        return noContent('User with id ' + idFromUrl() + ' not found.')
      }
    }

function handle route add in the switch area

case url.endsWith('/users') && method === 'POST':
  response = register();
  break;

case url.match(/\/users\/\d+$/) && method === 'PUT':
  response = changeUser();
  break;

Upgrade the unit test of the mock server

Add new unit tests to the file mock-backend.interceptor.service.spec.ts before the test function: it('should authenticate a user', (done) => {.

  it('should create a postUser with valid token and role admin', (done) => {
    httpClient.post<User>('/users', postUser, httpOptionsJwtToken)
      .subscribe((data) => {
          console.log('mockTestResponse', data);
          expect(data.status).toBe(200);
          expect(data.body.id).toBeGreaterThan(0);
          postUserId = data.body.id;
          done();
        },
        (err) => {
          console.log('mockTestResponse', err);
          done();
        }, () => {
          // console.log('Test complete');
          done();
        });
  });

  it('should change a postUser with valid token and role admin', (done) => {
    postUser.firstName = "changed";
    httpClient.put<User>('/users/' + postUserId, postUser, httpOptionsJwtToken)
      .subscribe((data) => {
          console.log('mockTestResponse', data);
          expect(data.status).toBe(200);
          expect(data.body.id).toBeGreaterThan(0);
          expect(data.body.id).toBe(postUser.id);
          expect(data.body.firstName).toEqual ("changed");
          done();
        },
        (err) => {
          console.log('mockTestResponse', err);
          done();
        }, () => {
          // console.log('Test complete');
          done();
        });
  });


  it('should delete the postUser with valid token and role admin', (done) => {
    httpClient.delete<User>('/users/' + postUserId,  httpOptionsJwtToken)
      .subscribe((data) => {
          console.log('mockTestResponse', data);
          expect(data.status).toBe(200);
          done();
        },
        (err) => {
          console.log('mockTestResponse', err);
          done();
          fail();
        }, () => {
          // console.log('Test complete');
          done();
        });
  });

Put the variable on the top section of the unit test file mock-backend-interceptor.spec.ts:

const postUser: User = {
  firstName: 'post',
  lastName: 'post',
  username: 'post',
  password: 'post'
};

let regUserId: number = 0;
let postUserId: number = 0;
let adminUserId: number = 0;

Create a user service

Please create a folder service in the folder src/app/shared. Create a file user.service.ts in the folder src/app/shared/service or
use the generator.

ng generate service shared/service/user
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {User} from '../model/user';
import {environment} from '../../../environments/environment';


@Injectable({providedIn: 'root'})
export class UserService {

  private authApiUrl: string;
  constructor(private http: HttpClient) {
    this.authApiUrl = environment.endpoints.backendAuthUrl
  }

  get(id: number) {
    return this.http.get<User>(`${this.authApiUrl}/users/${id}`);
  }

  getByName(name: string) {
    return this.http.get<User>(`${this.authApiUrl}/users/${name}`);
  }

  getAll() {
    return this.http.get<User[]>(`${this.authApiUrl}/users`);
  }

  register(user: User) {
    return this.http.post<any>(`${this.authApiUrl}/users/register`, user);
  }

  delete(id: number) {
    return this.http.delete(`this.authApiUrl/users/${id}`);
  }
}

Create the unit test file user.service.spec.ts in the folder src/app/shared/service.

import {TestBed} from '@angular/core/testing';
import {UserService} from './user.service';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {environment} from '../../../environments/environment';


describe('UserService', () => {
  let service: UserService;
  let httpMock;
  let expectedResult;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      providers: [
        UserService
      ]
    });
    expectedResult = {};
    service = TestBed.get(UserService);
    httpMock = TestBed.get(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should be created', () => {
    const service = TestBed.get(UserService);
    expect(service).toBeTruthy();
  });

  it('should call correct URL', () => {
    const userName = 'user';
    service.getByName(userName).subscribe(() => {});

    const req = httpMock.expectOne({ method: 'GET' });
    const resourceUrl = `${environment.endpoints.backendAuthUrl}/users/${userName}`;
    expect(req.request.url).toEqual(`${resourceUrl}`);
  });

  it('should return User', () => {
    const userName = 'user';
    service.getByName(userName).subscribe(received => {
      expectedResult = received;
    });

    const req = httpMock.expectOne({ method: 'GET' });
    const user = {
      firstName: userName,
      lastName: userName,
      username: userName,
      password: userName,
      email: userName + '@mail.com',
      thresholdOpenPayment: 1000,
      locked: false
    };
    req.flush(user);
    expect(expectedResult).toEqual(user);
  });

  it('should propagate not found response', () => {
    service.getByName('user').subscribe(null, (_error: any) => {
      expectedResult = _error.status;
    });

    const req = httpMock.expectOne({ method: 'GET' });
    req.flush('Invalid request parameters', {
      status: 404,
      statusText: 'Bad Request'
    });
    expect(expectedResult).toEqual(404);
  });
  
});

Create a service to create the default test users admin and user

We want to create default test and admin users for test purposes. We will use the register function to create:

username password role
user user user
admin admin user, admin

Create the file create-user.service.ts in the folder src/app/shared/helper.

ng generate service shared/helper/create-user
import {first} from 'rxjs/operators';
import {Injectable} from '@angular/core';
import {Router} from '@angular/router';
import {User} from '../model/user';
import {UserService} from '../service/user.service';

@Injectable({providedIn: 'root'})
export class CreateUserService {

  constructor(
    private router: Router,
    private userService: UserService
  ) {
  }

  private registerUser(user: User) {

    this.userService.register(user)
      .pipe(first())
      .subscribe(
        data => {},
        error => {}
        );
  }

  checkAndRegisterUser(userName) {

    let users = JSON.parse(localStorage.getItem('users')) || [];
    const user: User = users.find(x => x.username === userName );
    if (users.length === 0 || !user) {
      // user not yet in localStorage
      const user = {
        firstName: userName,
        lastName: userName,
        username: userName,
        password: userName,
        email: userName + '@mail.com',
        thresholdOpenPayment: 1000,
        locked: false
      };
      this.registerUser(user);
    }
  }

  /**
   * Creates default users for test purposes
   * Used only in development environment
   */
  createDefaultUsers() {
    this.checkAndRegisterUser('admin');
    this.checkAndRegisterUser('user');
  }

}

Create the unit test file create-user.service.spec.ts in the folder src/app/shared/helper.

import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import {CreateUserService} from './create-user.service';
import {User} from '../model/user';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {HTTP_INTERCEPTORS} from '@angular/common/http';
import {MockBackendInterceptor} from './mock/mock-backend-interceptor.service';

describe('CreateUserService', () => {

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      providers: [
        CreateUserService,
        {
          provide: HTTP_INTERCEPTORS,
          useClass: MockBackendInterceptor,
          multi: true
        }]
    });

  });

  it('should be created', () => {
    const service = TestBed.get(CreateUserService);
     expect(service).toBeTruthy();
  });

  it('should create a user', fakeAsync ( () => {
    const service = TestBed.get(CreateUserService);
    // create test user
    const testuser = 'testuser';

    service.checkAndRegisterUser(testuser);
    tick(20000);

    // check
    let users = JSON.parse(localStorage.getItem('users')) || [];
    const user: User = users.find(x => x.username === testuser && x.password === testuser);
    expect(user.username).toEqual(testuser);

    // delete testuser
    users = users.filter(x => x.id !== user.id);
    localStorage.setItem('users', JSON.stringify(users));
  }));

  async function delay(ms: number) {
    await new Promise(resolve => setTimeout(()=>resolve(), ms)).then(()=>console.log("fired"));
  }

});

Clone this wiki locally