Angular услуга спец. c не соответствует ожиданиям для наблюдаемых - PullRequest
0 голосов
/ 08 марта 2020

Проблема

Я пытаюсь написать spe c в жасмине для службы Angular, которая у меня есть. Служба упаковывает @azure/msal-angular, а также использует Microsoft Graph API для получения авторизованного изображения профиля пользователя. У меня проблемы с получением теста для успеха. Все, что я пытаюсь сделать, - это убедиться, что при возникновении ошибки Graph API при запросе изображения (т. Е. Возникает ошибка 404), эта ошибка обнаруживается, и вместо этого возвращается изображение по умолчанию из моей папки ресурсов.

Тем не менее, я получаю следующую ошибку: enter image description here

Ниже тестируется моя служба и сам тест. Мы ценим любую помощь!

Сервис

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { BroadcastService, MsalService } from '@azure/msal-angular';
import { Account, AuthError, AuthResponse, ClientAuthError } from 'msal';
import { Observable, ReplaySubject, throwError } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';

import { GraphUser } from '../../models';
import { LocalBlobService } from '../../services';
import { environment } from './../../../environments/environment';

@Injectable({
    providedIn: 'root',
})
export class AuthService {
    isLoggedIn: boolean;

    /** TODO: Cache locally
     * - clear on logout
     * - how often to refresh?
     */
    graphUser$: Observable<GraphUser>;

    /** TODO: Cache locally
     * - clear on logout
     * - how often to refresh?
     */
    graphPicture$: Observable<SafeUrl>;

    get loginFailure$(): Observable<any> {
        return this._loginFailure$.asObservable();
    }

    get loginSuccess$(): Observable<any> {
        return this._loginSuccess$.asObservable();
    }

    get user(): Account {
        const user = this._msal.getAccount();
        this.isLoggedIn = !!user;
        return user;
    }

    private _loginFailure$ = new ReplaySubject<any>();
    private _loginSuccess$ = new ReplaySubject<any>();

    constructor(
        private readonly _http: HttpClient,
        private readonly _msal: MsalService,
        private readonly _broadcasts: BroadcastService,
        private readonly _sanitizer: DomSanitizer,
        private readonly _localBlob: LocalBlobService
    ) {
        this._msal.handleRedirectCallback(this.redirectCallback);
        this.getGraphUserInfo();

        this._broadcasts.subscribe(
            'msal:loginFailure',
            this.loginFailureCallback
        );

        this._broadcasts.subscribe(
            'msal:loginSuccess',
            this.loginSuccessCallback
        );
    }

    updateUserProfilePhoto(file) {
        return this._http
            .put(environment.AD.pictureUrl, file)
            .pipe(
                catchError((error, caught) => this.handlePictureError(error))
            );
    }

    private getGraphUserInfo() {
        if (this.user) {
            this.graphUser$ = this.getGraphUser();
            this.graphPicture$ = this.getGraphPicture();
        }
    }

    private getGraphPicture(): Observable<SafeUrl> {
        return this._http
            .get(environment.AD.pictureUrl, {
                responseType: 'blob',
            })
            .pipe(
                catchError((error, caught) => this.handlePictureError(error)),
                switchMap(blob => this._localBlob.readAsDataURL(blob)),
                map(picture => this._sanitizer.bypassSecurityTrustUrl(picture)),
                shareReplay(1)
            );
    }

    private getGraphUser(): Observable<GraphUser> {
        return this._http.get<GraphUser>(environment.AD.graphUrl).pipe(
            catchError((error, caught) => throwError(error)),
            shareReplay()
        );
    }

    private loginSuccessCallback = payload => {
        this.isLoggedIn = true;
        this._loginSuccess$.next();
        this.getGraphUserInfo();
    };

    private loginFailureCallback = payload => {
        this.isLoggedIn = false;
        this._loginFailure$.next();
    };

    private redirectCallback = (
        redirectError: AuthError,
        redirectResponse: AuthResponse
    ) => {
        if (redirectError) {
            console.error(redirectError);
            return;
        }
        console.log(redirectResponse);
    };

    private handlePictureError(error: ClientAuthError): Observable<Blob> {
        console.log(error);
        return this._http.get('/assets/images/defaultAvatarSmall.png', {
            responseType: 'blob',
        });
    }
}

Тест

it('should return default image if graph API picture fails', (done: DoneFn) => {
    // Arrange
    const spy = spyOn(TestBed.inject(HttpClient), 'get')
        .withArgs(environment.AD.graphUrl)
        .and.callThrough()
        .withArgs(environment.AD.pictureUrl, {
            responseType: 'blob',
        } as any)
        .and.returnValue(throwError(new ClientAuthError('')))
        .withArgs('/assets/images/defaultAvatarSmall.png', {
            responseType: 'blob',
        } as any)
        .and.callThrough();

     // Act
    const service: AuthService = TestBed.inject(AuthService);
    service.graphPicture$.subscribe(
        () => {

            // Assert
            expect(spy.calls.allArgs()).toEqual([
                [environment.AD.graphUrl],
                [
                    environment.AD.pictureUrl,
                    { responseType: 'blob' as 'json' },
                ],
                [
                    '/assets/images/defaultAvatarSmall.png',
                    { responseType: 'blob' as 'json' },
                ],
            ]);
            done();
        },
        error => {
            expect(1).toEqual(1);
            done();
        }
    );
});

1 Ответ

0 голосов
/ 08 марта 2020

Я наконец понял это. Сегодня был длинный день из-за этого ...

Я изменил getGraphPicture в AuthService, чтобы определить его следующим образом:


    private getGraphPicture(): Observable<SafeUrl> {
        // Observable stream of image requests where errors are not emitted.
        return onErrorResumeNext<Blob>(
            // Try the logged in user's pictureUrl first
            this._http.get<Blob>(environment.AD.pictureUrl, {
                responseType: 'blob' as 'json',
            }),
            // Try the default avatar picture in assets next
            this._http.get<Blob>('/assets/images/defaultAvatarSmall.png', {
                responseType: 'blob' as 'json',
            })
        ).pipe(
            first(), // Only grab the first successful emission
            switchMap(blob => this._localBlob.readAsDataURL(blob)),
            map(picture => this._sanitizer.bypassSecurityTrustUrl(picture)),
            shareReplay(1)
        );
    }

Затем я написал свой тест следующим образом :


    it('should return default image if graph API picture fails', fakeAsync(() => {
        // Arrange
        const spy = spyOn(TestBed.inject(HttpClient), 'get')
            .withArgs(environment.AD.graphUrl)
            .and.callThrough()
            .withArgs(environment.AD.pictureUrl, {
                responseType: 'blob' as 'json',
            })
            .and.returnValue(throwError(new ClientAuthError('')))
            .withArgs('/assets/images/defaultAvatarSmall.png', {
                responseType: 'blob' as 'json',
            })
            .and.callThrough();

        // Act
        const service: AuthService = TestBed.inject(AuthService);
        tick();

        expect(spy.calls.allArgs()).toEqual([
            [environment.AD.graphUrl],
            [environment.AD.pictureUrl, { responseType: 'blob' as 'json' }],
            [
                '/assets/images/defaultAvatarSmall.png',
                { responseType: 'blob' as 'json' },
            ],
        ]);
    }));

...