Promise to observable within interceptor: how to accomplish both request and response handling?

  angular, javascript, rxjs

I have the following interceptor … note that I’m not doing anything of interest since I’m trying to debug my issue:

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { ApiService, IUserInfo } from "base-core";
import { from, Observable, of } from "rxjs";
import { map, mergeMap, tap } from "rxjs/operators";

@Injectable()
export class AddHeaderInterceptor implements HttpInterceptor {

    constructor(private api: ApiService) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        console.log(`AddHeaderInterceptor: adding user header to ${request.url}`);
        console.log(`Incoming URL: ${request.url}`);

        // return of('a') //from(['a'])
        //  .pipe(
        //      tap((userInfo: string) => console.log(`Obtained user info: ${userInfo}`)),
        //      map((userInfo: string) => request),
        //      tap((request: HttpRequest<any>) => console.log(`URL: ${request.url}`)),
        //      mergeMap((req: HttpRequest<any>) => next.handle(req) ),
        //      tap((data: HttpEvent<any>) => console.log(`Handled request: ${data.type}`))
        //  )

        return from(this.api.getUserInfo())
            .pipe(
                tap((userInfo: IUserInfo) => console.log(`Obtained user info: ${userInfo.user}`)),
                map((userInfo: IUserInfo) => request),
                tap((request: HttpRequest<any>) => console.log(`URL: ${request.url}`)),
                mergeMap((req: HttpRequest<any>) => next.handle(req) ),
                tap((data: HttpEvent<any>) => console.log(`Handled request: ${data.type}`))
            )
    }
}

… and the following test:

import { TestBed } from "@angular/core/testing";
import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
import { HTTP_INTERCEPTORS, HttpClient } from "@angular/common/http";
import { AddHeaderInterceptor } from "./add-header.interceptor";
import { ApiService, IUserInfo } from "base-core";

describe("AddHeaderInterceptor", () => {
  let httpTestingController: HttpTestingController,
    httpClient: HttpClient;

  beforeEach(() => {
        const apiServiceSpy = jasmine.createSpyObj("ApiService", {
            'getUserInfo': Promise.resolve<IUserInfo>({user: 'thatswhoiam', auth_pss: ['admin']})
        });

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        { provide: HTTP_INTERCEPTORS, useClass: AddHeaderInterceptor, multi: true},
        { provide: ApiService, useValue: apiServiceSpy}
      ]
    });

    httpClient = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  fit("should get the expected URL", () => {
    httpClient.get('/api/myPath').subscribe(
        (data: any) => console.log(`OnNext: ${data}`),
        err => console.log(`OnError: ${err}`),
        () => console.log(`OnComplete`)
    );

    let req = httpTestingController.expectOne('/api/myPath');
    req.flush([]);

  });
});

this.api.getUserInfo() return a Promise<IUserInfo>. I’m working off this link to convert the promise to an observable. I’ve tried both from and defer, but with either I see the same behavior in that only the HTTP request is handled, so the spec fails for Expected one matching request for criteria "Match URL: /api/myPath", found none. If I replace the interceptor content with the commented code at top (using either of or from, then both the request and response are handled and the unit test passes.

Why does HTTP handling end with request handling when I attempt to convert a promise to an observable, and is there something I can do to achieve both request and response handling?

Source: Angular Questions

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.