| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// <reference types="jasmine-ajax" />
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {HTTP_INTERCEPTORS, HttpBackend, HttpClient, HttpClientModule, HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; | 
					
						
							|  |  |  | import {Injectable} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  | import {TestBed, waitForAsync} from '@angular/core/testing'; | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | import {HttpClientBackendService, HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api'; | 
					
						
							|  |  |  | import {Observable, zip} from 'rxjs'; | 
					
						
							|  |  |  | import {concatMap, map, tap} from 'rxjs/operators'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {Hero} from './fixtures/hero'; | 
					
						
							|  |  |  | import {HeroInMemDataOverrideService} from './fixtures/hero-in-mem-data-override-service'; | 
					
						
							|  |  |  | import {HeroInMemDataService} from './fixtures/hero-in-mem-data-service'; | 
					
						
							|  |  |  | import {HeroService} from './fixtures/hero-service'; | 
					
						
							|  |  |  | import {HttpClientHeroService} from './fixtures/http-client-hero-service'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('HttpClient Backend Service', () => { | 
					
						
							|  |  |  |   const delay = 1;  // some minimal simulated latency delay
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('raw Angular HttpClient', () => { | 
					
						
							|  |  |  |     let http: HttpClient; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       TestBed.configureTestingModule({ | 
					
						
							|  |  |  |         imports: [ | 
					
						
							|  |  |  |           HttpClientModule, HttpClientInMemoryWebApiModule.forRoot(HeroInMemDataService, {delay}) | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       http = TestBed.get(HttpClient); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get heroes', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/heroes') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('GET should be a "cold" observable', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const httpBackend = TestBed.get(HttpBackend); | 
					
						
							|  |  |  |          const spy = spyOn(httpBackend, 'collectionHandler').and.callThrough(); | 
					
						
							|  |  |  |          const get$ = http.get<Hero[]>('api/heroes'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          // spy on `collectionHandler` should not be called before subscribe
 | 
					
						
							|  |  |  |          expect(spy).not.toHaveBeenCalled(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          get$.subscribe(heroes => { | 
					
						
							|  |  |  |            expect(spy).toHaveBeenCalled(); | 
					
						
							|  |  |  |            expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('GET should wait until after delay to respond', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          // to make test fail, set `delay=0` above
 | 
					
						
							|  |  |  |          let gotResponse = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          http.get<Hero[]>('api/heroes').subscribe(heroes => { | 
					
						
							|  |  |  |            gotResponse = true; | 
					
						
							|  |  |  |            expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          expect(gotResponse).toBe(false, 'should delay before response'); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('Should only initialize the db once', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const httpBackend = TestBed.get(HttpBackend); | 
					
						
							|  |  |  |          const spy = spyOn(httpBackend, 'resetDb').and.callThrough(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          // Simultaneous backend.handler calls
 | 
					
						
							|  |  |  |          // Only the first should initialize by calling `resetDb`
 | 
					
						
							|  |  |  |          // All should wait until the db is "ready"
 | 
					
						
							|  |  |  |          // then they share the same db instance.
 | 
					
						
							|  |  |  |          http.get<Hero[]>('api/heroes').subscribe(); | 
					
						
							|  |  |  |          http.get<Hero[]>('api/heroes').subscribe(); | 
					
						
							|  |  |  |          http.get<Hero[]>('api/heroes').subscribe(); | 
					
						
							|  |  |  |          http.get<Hero[]>('api/heroes').subscribe(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          expect(spy.calls.count()).toBe(1); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get heroes (w/ a different base path)', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('some-base-path/heroes').subscribe(heroes => { | 
					
						
							|  |  |  |            expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should 404 when GET unknown collection (after delay)', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          let gotError = false; | 
					
						
							|  |  |  |          const url = 'api/unknown-collection'; | 
					
						
							|  |  |  |          http.get<Hero[]>(url).subscribe( | 
					
						
							|  |  |  |              () => fail(`should not have found data for '${url}'`), err => { | 
					
						
							|  |  |  |                gotError = true; | 
					
						
							|  |  |  |                expect(err.status).toBe(404, 'should have 404 status'); | 
					
						
							|  |  |  |              }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          expect(gotError).toBe(false, 'should not get error until after delay'); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should return the hero w/id=1 for GET app/heroes/1', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero>('api/heroes/1') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  hero => expect(hero).toBeDefined('should find hero with id=1'), failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // test where id is string that looks like a number
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should return the stringer w/id="10" for GET app/stringers/10', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero>('api/stringers/10') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  hero => expect(hero).toBeDefined('should find string with id="10"'), failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should return 1-item array for GET app/heroes/?id=1', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/heroes/?id=1') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  heroes => expect(heroes.length).toBe(1, 'should find one hero w/id=1'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should return 1-item array for GET app/heroes?id=1', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/heroes?id=1') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  heroes => expect(heroes.length).toBe(1, 'should find one hero w/id=1'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should return undefined for GET app/heroes?id=not-found-id', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/heroes?id=123456') | 
					
						
							|  |  |  |              .subscribe(heroes => expect(heroes.length).toBe(0), failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should return 404 for GET app/heroes/not-found-id', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const url = 'api/heroes/123456'; | 
					
						
							|  |  |  |          http.get<Hero[]>(url).subscribe( | 
					
						
							|  |  |  |              () => fail(`should not have found data for '${url}'`), | 
					
						
							|  |  |  |              err => expect(err.status).toBe(404, 'should have 404 status')); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can generate the id when add a hero with no id', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const hero = new Hero(undefined, 'SuperDooper'); | 
					
						
							|  |  |  |          http.post<Hero>('api/heroes', hero).subscribe(replyHero => { | 
					
						
							|  |  |  |            expect(replyHero.id).toBeDefined('added hero should have an id'); | 
					
						
							|  |  |  |            expect(replyHero).not.toBe(hero, 'reply hero should not be the request hero'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get nobodies (empty collection)', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/nobodies').subscribe(nobodies => { | 
					
						
							|  |  |  |            expect(nobodies.length).toBe(0, 'should have no nobodies'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can add a nobody with an id to empty nobodies collection', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const id = 'g-u-i-d'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          http.post('api/nobodies', {id, name: 'Noman'}) | 
					
						
							|  |  |  |              .pipe(concatMap(() => http.get<{id: string; name: string;}[]>('api/nobodies'))) | 
					
						
							|  |  |  |              .subscribe(nobodies => { | 
					
						
							|  |  |  |                expect(nobodies.length).toBe(1, 'should a nobody'); | 
					
						
							|  |  |  |                expect(nobodies[0].name).toBe('Noman', 'should be "Noman"'); | 
					
						
							|  |  |  |                expect(nobodies[0].id).toBe(id, 'should preserve the submitted, ' + id); | 
					
						
							|  |  |  |              }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should fail when add a nobody without an id to empty nobodies collection', | 
					
						
							|  |  |  |        waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.post('api/nobodies', {name: 'Noman'}) | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  () => fail(`should not have been able to add 'Norman' to 'nobodies'`), err => { | 
					
						
							|  |  |  |                    expect(err.status).toBe(422, 'should have 422 status'); | 
					
						
							|  |  |  |                    expect(err.body.error).toContain('id type is non-numeric'); | 
					
						
							|  |  |  |                  }); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('can reset the database', () => { | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('to empty (object db)', waitForAsync(() => resetDatabaseTest('object'))); | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('to empty (observable db)', waitForAsync(() => resetDatabaseTest('observable'))); | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('to empty (promise db)', waitForAsync(() => resetDatabaseTest('promise'))); | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       function resetDatabaseTest(returnType: string) { | 
					
						
							|  |  |  |         // Observable of the number of heroes and nobodies
 | 
					
						
							|  |  |  |         const sizes$ = | 
					
						
							|  |  |  |             zip(http.get<Hero[]>('api/heroes'), http.get<Hero[]>('api/nobodies'), | 
					
						
							|  |  |  |                 http.get<Hero[]>('api/stringers')) | 
					
						
							|  |  |  |                 .pipe(map( | 
					
						
							|  |  |  |                     ([h, n, s]) => ({heroes: h.length, nobodies: n.length, stringers: s.length}))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Add a nobody so that we have one
 | 
					
						
							|  |  |  |         http.post('api/nobodies', {id: 42, name: 'Noman'}) | 
					
						
							|  |  |  |             .pipe( | 
					
						
							|  |  |  |                 // Reset database with "clear" option
 | 
					
						
							|  |  |  |                 concatMap(() => http.post('commands/resetDb', {clear: true, returnType})), | 
					
						
							|  |  |  |                 // get the number of heroes and nobodies
 | 
					
						
							|  |  |  |                 concatMap(() => sizes$)) | 
					
						
							|  |  |  |             .subscribe(sizes => { | 
					
						
							|  |  |  |               expect(sizes.heroes).toBe(0, 'reset should have cleared the heroes'); | 
					
						
							|  |  |  |               expect(sizes.nobodies).toBe(0, 'reset should have cleared the nobodies'); | 
					
						
							|  |  |  |               expect(sizes.stringers).toBe(0, 'reset should have cleared the stringers'); | 
					
						
							|  |  |  |             }, failRequest); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('raw Angular HttpClient w/ override service', () => { | 
					
						
							|  |  |  |     let http: HttpClient; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       TestBed.configureTestingModule({ | 
					
						
							|  |  |  |         imports: [ | 
					
						
							|  |  |  |           HttpClientModule, | 
					
						
							|  |  |  |           HttpClientInMemoryWebApiModule.forRoot(HeroInMemDataOverrideService, {delay}) | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       http = TestBed.get(HttpClient); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get heroes', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/heroes') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can translate `foo/heroes` to `heroes` via `parsedRequestUrl` override', | 
					
						
							|  |  |  |        waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/foo/heroes') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get villains', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/villains') | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  villains => expect(villains.length).toBeGreaterThan(0, 'should have villains'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should 404 when POST to villains', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const url = 'api/villains'; | 
					
						
							|  |  |  |          http.post<Hero[]>(url, {id: 42, name: 'Dr. Evil'}) | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  () => fail(`should not have POSTed data for '${url}'`), | 
					
						
							|  |  |  |                  err => expect(err.status).toBe(404, 'should have 404 status')); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should 404 when GET unknown collection', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const url = 'api/unknown-collection'; | 
					
						
							|  |  |  |          http.get<Hero[]>(url).subscribe( | 
					
						
							|  |  |  |              () => fail(`should not have found data for '${url}'`), | 
					
						
							|  |  |  |              err => expect(err.status).toBe(404, 'should have 404 status')); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should use genId override to add new hero, "Maxinius"', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.post('api/heroes', {name: 'Maxinius'}) | 
					
						
							|  |  |  |              .pipe(concatMap(() => http.get<Hero[]>('api/heroes?name=Maxi'))) | 
					
						
							|  |  |  |              .subscribe(heroes => { | 
					
						
							|  |  |  |                expect(heroes.length).toBe(1, 'should have found "Maxinius"'); | 
					
						
							|  |  |  |                expect(heroes[0].name).toBe('Maxinius'); | 
					
						
							|  |  |  |                expect(heroes[0].id).toBeGreaterThan(1000); | 
					
						
							|  |  |  |              }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should use genId override guid generator for a new nobody without an id', | 
					
						
							|  |  |  |        waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.post('api/nobodies', {name: 'Noman'}) | 
					
						
							|  |  |  |              .pipe(concatMap(() => http.get<{id: string; name: string}[]>('api/nobodies'))) | 
					
						
							|  |  |  |              .subscribe(nobodies => { | 
					
						
							|  |  |  |                expect(nobodies.length).toBe(1, 'should a nobody'); | 
					
						
							|  |  |  |                expect(nobodies[0].name).toBe('Noman', 'should be "Noman"'); | 
					
						
							|  |  |  |                expect(typeof nobodies[0].id).toBe('string', 'should create a string (guid) id'); | 
					
						
							|  |  |  |              }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('can reset the database', () => { | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('to empty (object db)', waitForAsync(() => resetDatabaseTest('object'))); | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('to empty (observable db)', waitForAsync(() => resetDatabaseTest('observable'))); | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('to empty (promise db)', waitForAsync(() => resetDatabaseTest('promise'))); | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       function resetDatabaseTest(returnType: string) { | 
					
						
							|  |  |  |         // Observable of the number of heroes, nobodies and villains
 | 
					
						
							|  |  |  |         const sizes$ = zip(http.get<Hero[]>('api/heroes'), http.get<Hero[]>('api/nobodies'), | 
					
						
							|  |  |  |                            http.get<Hero[]>('api/stringers'), http.get<Hero[]>('api/villains')) | 
					
						
							|  |  |  |                            .pipe(map(([h, n, s, v]) => ({ | 
					
						
							|  |  |  |                                        heroes: h.length, | 
					
						
							|  |  |  |                                        nobodies: n.length, | 
					
						
							|  |  |  |                                        stringers: s.length, | 
					
						
							|  |  |  |                                        villains: v.length | 
					
						
							|  |  |  |                                      }))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Add a nobody so that we have one
 | 
					
						
							|  |  |  |         http.post('api/nobodies', {id: 42, name: 'Noman'}) | 
					
						
							|  |  |  |             .pipe( | 
					
						
							|  |  |  |                 // Reset database with "clear" option
 | 
					
						
							|  |  |  |                 concatMap(() => http.post('commands/resetDb', {clear: true, returnType})), | 
					
						
							|  |  |  |                 // count all the collections
 | 
					
						
							|  |  |  |                 concatMap(() => sizes$)) | 
					
						
							|  |  |  |             .subscribe(sizes => { | 
					
						
							|  |  |  |               expect(sizes.heroes).toBe(0, 'reset should have cleared the heroes'); | 
					
						
							|  |  |  |               expect(sizes.nobodies).toBe(0, 'reset should have cleared the nobodies'); | 
					
						
							|  |  |  |               expect(sizes.stringers).toBe(0, 'reset should have cleared the stringers'); | 
					
						
							|  |  |  |               expect(sizes.villains).toBeGreaterThan(0, 'reset should NOT clear villains'); | 
					
						
							|  |  |  |             }, failRequest); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('HttpClient HeroService', () => { | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       TestBed.configureTestingModule({ | 
					
						
							|  |  |  |         imports: [ | 
					
						
							|  |  |  |           HttpClientModule, HttpClientInMemoryWebApiModule.forRoot(HeroInMemDataService, {delay}) | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         providers: [{provide: HeroService, useClass: HttpClientHeroService}] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('HeroService core', () => { | 
					
						
							|  |  |  |       let heroService: HeroService; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       beforeEach(() => { | 
					
						
							|  |  |  |         heroService = TestBed.get(HeroService); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('can get heroes', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            heroService.getHeroes().subscribe(heroes => { | 
					
						
							|  |  |  |              expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |            }, failRequest); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('can get hero w/ id=1', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            heroService.getHero(1).subscribe(hero => { | 
					
						
							|  |  |  |              expect(hero.name).toBe('Windstorm'); | 
					
						
							|  |  |  |            }, () => fail('getHero failed')); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('should 404 when hero id not found', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            const id = 123456; | 
					
						
							|  |  |  |            heroService.getHero(id).subscribe( | 
					
						
							|  |  |  |                () => fail(`should not have found hero for id='${id}'`), err => { | 
					
						
							|  |  |  |                  expect(err.status).toBe(404, 'should have 404 status'); | 
					
						
							|  |  |  |                }); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('can add a hero', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            heroService.addHero('FunkyBob') | 
					
						
							|  |  |  |                .pipe( | 
					
						
							|  |  |  |                    tap(hero => expect(hero.name).toBe('FunkyBob')), | 
					
						
							|  |  |  |                    // Get the new hero by its generated id
 | 
					
						
							|  |  |  |                    concatMap(hero => heroService.getHero(hero.id))) | 
					
						
							|  |  |  |                .subscribe(hero => { | 
					
						
							|  |  |  |                  expect(hero.name).toBe('FunkyBob'); | 
					
						
							|  |  |  |                }, () => failRequest('re-fetch of new hero failed')); | 
					
						
							|  |  |  |          }), | 
					
						
							|  |  |  |          10000); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('can delete a hero', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            const id = 1; | 
					
						
							|  |  |  |            heroService.deleteHero(id).subscribe((_: {}) => expect(_).toBeDefined(), failRequest); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('should allow delete of non-existent hero', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            const id = 123456; | 
					
						
							|  |  |  |            heroService.deleteHero(id).subscribe((_: {}) => expect(_).toBeDefined(), failRequest); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('can search for heroes by name containing "a"', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            heroService.searchHeroes('a').subscribe((heroes: Hero[]) => { | 
					
						
							|  |  |  |              expect(heroes.length).toBe(3, 'should find 3 heroes with letter "a"'); | 
					
						
							|  |  |  |            }, failRequest); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('can update existing hero', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            const id = 1; | 
					
						
							|  |  |  |            heroService.getHero(id) | 
					
						
							|  |  |  |                .pipe( | 
					
						
							|  |  |  |                    concatMap(hero => { | 
					
						
							|  |  |  |                      hero.name = 'Thunderstorm'; | 
					
						
							|  |  |  |                      return heroService.updateHero(hero); | 
					
						
							|  |  |  |                    }), | 
					
						
							|  |  |  |                    concatMap(() => heroService.getHero(id))) | 
					
						
							|  |  |  |                .subscribe( | 
					
						
							|  |  |  |                    hero => expect(hero.name).toBe('Thunderstorm'), | 
					
						
							|  |  |  |                    () => fail('re-fetch of updated hero failed')); | 
					
						
							|  |  |  |          }), | 
					
						
							|  |  |  |          10000); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |       it('should create new hero when try to update non-existent hero', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |            const falseHero = new Hero(12321, 'DryMan'); | 
					
						
							|  |  |  |            heroService.updateHero(falseHero).subscribe( | 
					
						
							|  |  |  |                hero => expect(hero.name).toBe(falseHero.name), failRequest); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('HttpClient interceptor', () => { | 
					
						
							|  |  |  |     let http: HttpClient; | 
					
						
							|  |  |  |     let interceptors: HttpInterceptor[]; | 
					
						
							|  |  |  |     let httpBackend: HttpClientBackendService; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Test interceptor adds a request header and a response header | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     @Injectable() | 
					
						
							|  |  |  |     class TestHeaderInterceptor implements HttpInterceptor { | 
					
						
							|  |  |  |       intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | 
					
						
							|  |  |  |         const reqClone = req.clone({setHeaders: {'x-test-req': 'req-test-header'}}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return next.handle(reqClone).pipe(map(event => { | 
					
						
							|  |  |  |           if (event instanceof HttpResponse) { | 
					
						
							|  |  |  |             event = event.clone({headers: event.headers.set('x-test-res', 'res-test-header')}); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return event; | 
					
						
							|  |  |  |         })); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       TestBed.configureTestingModule({ | 
					
						
							|  |  |  |         imports: [ | 
					
						
							|  |  |  |           HttpClientModule, HttpClientInMemoryWebApiModule.forRoot(HeroInMemDataService, {delay}) | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         providers: [ | 
					
						
							|  |  |  |           // Add test interceptor just for this test suite
 | 
					
						
							|  |  |  |           {provide: HTTP_INTERCEPTORS, useClass: TestHeaderInterceptor, multi: true} | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       http = TestBed.get(HttpClient); | 
					
						
							|  |  |  |       httpBackend = TestBed.get(HttpBackend); | 
					
						
							|  |  |  |       interceptors = TestBed.get(HTTP_INTERCEPTORS); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // sanity test
 | 
					
						
							|  |  |  |     it('TestingModule should provide the test interceptor', () => { | 
					
						
							|  |  |  |       const ti = interceptors.find(i => i instanceof TestHeaderInterceptor); | 
					
						
							|  |  |  |       expect(ti).toBeDefined(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should have GET request header from test interceptor', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          const handle = spyOn(httpBackend, 'handle').and.callThrough(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          http.get<Hero[]>('api/heroes').subscribe(heroes => { | 
					
						
							|  |  |  |            // HttpRequest is first arg of the first call to in-mem backend `handle`
 | 
					
						
							|  |  |  |            const req: HttpRequest<Hero[]> = handle.calls.argsFor(0)[0]; | 
					
						
							|  |  |  |            const reqHeader = req.headers.get('x-test-req'); | 
					
						
							|  |  |  |            expect(reqHeader).toBe('req-test-header'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('should have GET response header from test interceptor', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          let gotResponse = false; | 
					
						
							|  |  |  |          const req = new HttpRequest<any>('GET', 'api/heroes'); | 
					
						
							|  |  |  |          http.request<Hero[]>(req).subscribe(event => { | 
					
						
							|  |  |  |            if (event.type === HttpEventType.Response) { | 
					
						
							|  |  |  |              gotResponse = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |              const resHeader = event.headers.get('x-test-res'); | 
					
						
							|  |  |  |              expect(resHeader).toBe('res-test-header'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |              const heroes = event.body as Hero[]; | 
					
						
							|  |  |  |              expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |            } | 
					
						
							|  |  |  |          }, failRequest, () => expect(gotResponse).toBe(true, 'should have seen Response event')); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('HttpClient passThru', () => { | 
					
						
							|  |  |  |     let http: HttpClient; | 
					
						
							|  |  |  |     let httpBackend: HttpClientBackendService; | 
					
						
							|  |  |  |     let createPassThruBackend: jasmine.Spy; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       TestBed.configureTestingModule({ | 
					
						
							|  |  |  |         imports: [ | 
					
						
							|  |  |  |           HttpClientModule, | 
					
						
							|  |  |  |           HttpClientInMemoryWebApiModule.forRoot( | 
					
						
							|  |  |  |               HeroInMemDataService, {delay, passThruUnknownUrl: true}) | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       http = TestBed.get(HttpClient); | 
					
						
							|  |  |  |       httpBackend = TestBed.get(HttpBackend); | 
					
						
							|  |  |  |       createPassThruBackend = spyOn(<any>httpBackend, 'createPassThruBackend').and.callThrough(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       jasmine.Ajax.install(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     afterEach(() => { | 
					
						
							|  |  |  |       jasmine.Ajax.uninstall(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get heroes (no passthru)', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<Hero[]>('api/heroes').subscribe(heroes => { | 
					
						
							|  |  |  |            expect(createPassThruBackend).not.toHaveBeenCalled(); | 
					
						
							|  |  |  |            expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // `passthru` is NOT a collection in the data store
 | 
					
						
							|  |  |  |     // so requests for it should pass thru to the "real" server
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can GET passthru', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          jasmine.Ajax.stubRequest('api/passthru').andReturn({ | 
					
						
							|  |  |  |            'status': 200, | 
					
						
							|  |  |  |            'contentType': 'application/json', | 
					
						
							|  |  |  |            'response': JSON.stringify([{id: 42, name: 'Dude'}]) | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          http.get<any[]>('api/passthru').subscribe(passthru => { | 
					
						
							|  |  |  |            expect(passthru.length).toBeGreaterThan(0, 'should have passthru data'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can ADD to passthru', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          jasmine.Ajax.stubRequest('api/passthru').andReturn({ | 
					
						
							|  |  |  |            'status': 200, | 
					
						
							|  |  |  |            'contentType': 'application/json', | 
					
						
							|  |  |  |            'response': JSON.stringify({id: 42, name: 'Dude'}) | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          http.post<any>('api/passthru', {name: 'Dude'}).subscribe(passthru => { | 
					
						
							|  |  |  |            expect(passthru).toBeDefined('should have passthru data'); | 
					
						
							|  |  |  |            expect(passthru.id).toBe(42, 'passthru object should have id 42'); | 
					
						
							|  |  |  |          }, failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('Http dataEncapsulation = true', () => { | 
					
						
							|  |  |  |     let http: HttpClient; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       TestBed.configureTestingModule({ | 
					
						
							|  |  |  |         imports: [ | 
					
						
							|  |  |  |           HttpClientModule, | 
					
						
							|  |  |  |           HttpClientInMemoryWebApiModule.forRoot( | 
					
						
							|  |  |  |               HeroInMemDataService, {delay, dataEncapsulation: true}) | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       http = TestBed.get(HttpClient); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-01 04:43:18 +09:00
										 |  |  |     it('can get heroes (encapsulated)', waitForAsync(() => { | 
					
						
							| 
									
										
										
										
											2020-06-12 18:25:08 +02:00
										 |  |  |          http.get<{data: any}>('api/heroes') | 
					
						
							|  |  |  |              .pipe(map(data => data.data as Hero[])) | 
					
						
							|  |  |  |              .subscribe( | 
					
						
							|  |  |  |                  heroes => expect(heroes.length).toBeGreaterThan(0, 'should have data.heroes'), | 
					
						
							|  |  |  |                  failRequest); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Fail a Jasmine test such that it displays the error object, | 
					
						
							|  |  |  |  * typically passed in the error path of an Observable.subscribe() | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function failRequest(err: any) { | 
					
						
							|  |  |  |   fail(JSON.stringify(err)); | 
					
						
							|  |  |  | } |