Skip to content

Commit

Permalink
fix(daffio): /api soft 404s on SSR (#2712)
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 committed Jan 18, 2024
1 parent 2f550fc commit f58c266
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 51 deletions.
2 changes: 1 addition & 1 deletion apps/daffio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"lint": "cd ../.. && ng lint daffio",
"lint:fix": "npm run lint -- --fix",
"dev:ssr": "ng run daffio:serve-ssr",
"serve:ssr": "DAFFIO_DOCS_PATH=http://localhost:4000/assets/daffio/docs/ node ../../dist/apps/daffio/server/main.js"
"serve:ssr": "DAFFIO_DOCS_PATH=../../dist/apps/daffio/browser/assets/daffio/docs/ node ../../dist/apps/daffio/server/main.js"
},
"homepage": "https://github.com/graycoreio/daffodil",
"description": "A documentation site for the daffodil project",
Expand Down
36 changes: 17 additions & 19 deletions apps/daffio/src/app/api/services/api.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';

import { DaffioApiService } from './api.service';
import {
DaffioAssetFetchServiceInterface,
DaffioAssetFetchService,
} from '../../core/assets/fetch/service.interface';

describe('DaffioApiService', () => {
let httpTestingController: HttpTestingController;
let fetchAssetServiceSpy: jasmine.SpyObj<DaffioAssetFetchServiceInterface>;
let service: DaffioApiService;

beforeEach(() => {
fetchAssetServiceSpy = jasmine.createSpyObj('DaffioAssetFetchService', ['fetch']);

TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DaffioApiService,
{
provide: DaffioAssetFetchService,
useValue: fetchAssetServiceSpy,
},
],
});

httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(DaffioApiService);
});

afterEach(() => {
// After every test, assert that there are no more pending requests.
httpTestingController.verify();
});

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

describe('getApiList', () => {

it('should make a get request', () => {
it('should make a get request', (done) => {
fetchAssetServiceSpy.fetch.and.returnValue(of([]));

service.list().subscribe((docsList) => {
expect(docsList).toEqual([]);
expect(fetchAssetServiceSpy.fetch).toHaveBeenCalledWith('/assets/daffio/docs/api/api-list.json');
done();
});
const req = httpTestingController.expectOne('/assets/daffio/docs/api/api-list.json');

expect(req.request.method).toEqual('GET');

req.flush([]);
});
});
});
9 changes: 6 additions & 3 deletions apps/daffio/src/app/api/services/api.service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { HttpClient } from '@angular/common/http';
import {
Inject,
Injectable,
} from '@angular/core';
import { Observable } from 'rxjs';

import { DaffioApiServiceInterface } from './api-service.interface';
import {
DaffioAssetFetchService,
DaffioAssetFetchServiceInterface,
} from '../../core/assets/fetch/service.interface';
import { DAFFIO_DOCS_PATH_TOKEN } from '../../docs/services/docs-path.token';
import { DaffioApiReference } from '../models/api-reference';

@Injectable({ providedIn: 'root' })
export class DaffioApiService implements DaffioApiServiceInterface {

constructor(
private http: HttpClient,
@Inject(DaffioAssetFetchService) private fetchAsset: DaffioAssetFetchServiceInterface,
@Inject(DAFFIO_DOCS_PATH_TOKEN) private docsPath: string,
) {}

list(): Observable<DaffioApiReference[]> {
return this.http.get<DaffioApiReference[]>(this.docsPath + 'api/api-list.json');
return this.fetchAsset.fetch<DaffioApiReference[]>(`${this.docsPath}api/api-list.json`);
}
}
6 changes: 6 additions & 0 deletions apps/daffio/src/app/app.server.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ServerModule } from '@angular/platform-server';

import { DaffioAppComponent } from './app.component';
import { AppModule } from './app.module';
import { DaffioAssetFetchServerService } from './core/assets/fetch/server.service';
import { DaffioAssetFetchService } from './core/assets/fetch/service.interface';
import { DAFFIO_DOCS_PATH_TOKEN } from './docs/services/docs-path.token';

@NgModule({
Expand All @@ -16,6 +18,10 @@ import { DAFFIO_DOCS_PATH_TOKEN } from './docs/services/docs-path.token';
provide: DAFFIO_DOCS_PATH_TOKEN,
useValue: process.env.DAFFIO_DOCS_PATH || '',
},
{
provide: DaffioAssetFetchService,
useExisting: DaffioAssetFetchServerService,
},
],
})
export class AppServerModule {}
46 changes: 46 additions & 0 deletions apps/daffio/src/app/core/assets/fetch/browser.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';

import { DaffioAssetFetchBrowserService } from './browser.service';

describe('DaffioAssetFetchBrowserService', () => {
let httpTestingController: HttpTestingController;
let service: DaffioAssetFetchBrowserService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DaffioAssetFetchBrowserService,
],
});

httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(DaffioAssetFetchBrowserService);
});

afterEach(() => {
// After every test, assert that there are no more pending requests.
httpTestingController.verify();
});

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

describe('getApiList', () => {
it('should make a get request', () => {
service.fetch('path').subscribe((docsList) => {
expect(docsList).toEqual([]);
});
const req = httpTestingController.expectOne('path');

expect(req.request.method).toEqual('GET');

req.flush([]);
});
});
});
16 changes: 16 additions & 0 deletions apps/daffio/src/app/core/assets/fetch/browser.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

import { DaffioAssetFetchServiceInterface } from './service.interface';

@Injectable({ providedIn: 'root' })
export class DaffioAssetFetchBrowserService implements DaffioAssetFetchServiceInterface {
constructor(
private http: HttpClient,
) {}

fetch<T = unknown>(path: string): Observable<T> {
return this.http.get<T>(path);
}
}
18 changes: 18 additions & 0 deletions apps/daffio/src/app/core/assets/fetch/server.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { readFile } from 'node:fs/promises';
import {
Observable,
from,
map,
} from 'rxjs';

import { DaffioAssetFetchServiceInterface } from './service.interface';

@Injectable({ providedIn: 'root' })
export class DaffioAssetFetchServerService implements DaffioAssetFetchServiceInterface {
fetch<T = unknown>(path: string): Observable<T> {
return from(readFile(path)).pipe(
map((buffer) => JSON.parse(buffer.toString())),
);
}
}
18 changes: 18 additions & 0 deletions apps/daffio/src/app/core/assets/fetch/service.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
InjectionToken,
inject,
} from '@angular/core';
import { Observable } from 'rxjs';

import { DaffioAssetFetchBrowserService } from './browser.service';

export interface DaffioAssetFetchServiceInterface {
fetch<T = unknown>(path: string): Observable<T>;
}

export const DaffioAssetFetchService = new InjectionToken<DaffioAssetFetchServiceInterface>(
'DaffioAssetFetchService',
{
factory: () => inject(DaffioAssetFetchBrowserService),
},
);
50 changes: 26 additions & 24 deletions apps/daffio/src/app/docs/services/docs.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,60 @@
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';

import { DaffioDocsService } from './docs.service';
import {
DaffioAssetFetchService,
DaffioAssetFetchServiceInterface,
} from '../../core/assets/fetch/service.interface';
import { DaffioDoc } from '../models/doc';
import { DaffioGuideList } from '../models/guide-list';
import { DaffioDocsFactory } from '../testing/factories/docs.factory';
import { mockGuides } from '../testing/factories/guide-list.factory';

describe('DaffioDocsService', () => {
let service: DaffioDocsService<DaffioDoc, DaffioGuideList>;
let httpTestingController: HttpTestingController;
const doc = new DaffioDocsFactory().create();
let fetchAssetServiceSpy: jasmine.SpyObj<DaffioAssetFetchServiceInterface>;
let doc: DaffioDoc;
const mockGuideList = mockGuides;

beforeEach(() => {
fetchAssetServiceSpy = jasmine.createSpyObj('DaffioAssetFetchService', ['fetch']);

TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{
provide: DaffioAssetFetchService,
useValue: fetchAssetServiceSpy,
},
],
});

httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(DaffioDocsService);
});

afterEach(() => {
// After every test, assert that there are no more pending requests.
httpTestingController.verify();
doc = TestBed.inject(DaffioDocsFactory).create();
});

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

it('should be able to retrieve a doc', () => {
it('should be able to retrieve a doc', (done) => {
fetchAssetServiceSpy.fetch.and.returnValue(of(doc));

service.get('my/path').subscribe((apiDoc) => {
expect(apiDoc).toEqual(doc);
expect(fetchAssetServiceSpy.fetch).toHaveBeenCalledWith('/assets/daffio/docs/my/path.json');
done();
});
const req = httpTestingController.expectOne('/assets/daffio/docs/my/path.json');

expect(req.request.method).toEqual('GET');

req.flush(doc);
});

it('should be able to retrieve a guide list', () => {
it('should be able to retrieve a guide list', (done) => {
fetchAssetServiceSpy.fetch.and.returnValue(of(mockGuideList));

service.getGuideList().subscribe((guides) => {
expect(guides).toEqual(mockGuideList);
expect(fetchAssetServiceSpy.fetch).toHaveBeenCalledWith('/assets/daffio/docs/guides/guide-list.json');
done();
});
const req = httpTestingController.expectOne('/assets/daffio/docs/guides/guide-list.json');

expect(req.request.method).toEqual('GET');

req.flush(mockGuideList);
});
});
11 changes: 7 additions & 4 deletions apps/daffio/src/app/docs/services/docs.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { HttpClient } from '@angular/common/http';
import {
Inject,
Injectable,
Expand All @@ -9,6 +8,10 @@ import { crossOsFilename } from '@daffodil/docs-utils';

import { DAFFIO_DOCS_PATH_TOKEN } from './docs-path.token';
import { DaffioDocsServiceInterface } from './docs-service.interface';
import {
DaffioAssetFetchService,
DaffioAssetFetchServiceInterface,
} from '../../core/assets/fetch/service.interface';
import { DaffioDoc } from '../models/doc';
import { DaffioGuideList } from '../models/guide-list';

Expand All @@ -18,15 +21,15 @@ import { DaffioGuideList } from '../models/guide-list';
export class DaffioDocsService<T extends DaffioDoc = DaffioDoc, V extends DaffioGuideList = DaffioGuideList> implements DaffioDocsServiceInterface<T, V> {

constructor(
private http: HttpClient,
@Inject(DaffioAssetFetchService) private fetchAsset: DaffioAssetFetchServiceInterface,
@Inject(DAFFIO_DOCS_PATH_TOKEN) private docsPath: string,
) {}

get(path: string): Observable<T> {
return this.http.get<T>(this.docsPath + crossOsFilename(path) + '.json');
return this.fetchAsset.fetch<T>(`${this.docsPath}${crossOsFilename(path)}.json`);
}

getGuideList(): Observable<V> {
return this.http.get<V>(this.docsPath + 'guides/guide-list.json');
return this.fetchAsset.fetch<V>(`${this.docsPath}guides/guide-list.json`);
}
}

0 comments on commit f58c266

Please sign in to comment.