From b2440ebc67dff0af4b3d28ce4c41e6d0023e9c8a Mon Sep 17 00:00:00 2001 From: George Svarovsky Date: Fri, 28 Jul 2023 12:46:16 +0100 Subject: [PATCH] #6: Serving docs with gateway identity filled-in --- doc/README.md | 8 +++-- doc/_includes/index-example.js | 4 +-- doc/accounts.md | 10 +++--- doc/clone-api.md | 6 ++-- doc/getting-started.md | 8 ++--- doc/http-client.env.json | 2 +- doc/named-subdomains.md | 4 +-- doc/uuid-subdomains.md | 7 ++--- src/http/EndPoint.ts | 6 ++-- src/http/GatewayWebsite.ts | 55 ++++++++++++++++++++++++--------- src/http/errors.ts | 10 +++--- src/http/index.ts | 20 ++++++++---- src/lib/BaseGateway.ts | 23 +++++++------- src/server/Gateway.ts | 7 ++--- src/socket.io/IoCloneFactory.ts | 4 +-- src/start.ts | 12 +++++-- test/http.test.ts | 36 +++++++++++++++++++-- 17 files changed, 145 insertions(+), 77 deletions(-) diff --git a/doc/README.md b/doc/README.md index 2b90935..a68e385 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,5 +1,9 @@ # m-ld Gateway Documentation -This `doc` folder is intended to form the basis of a documentation portal for the **m-ld** Gateway, published on the web via a CDN. +This `doc` folder forms the basis of the **m-ld** Gateway public documentation. -The entry point is [Getting Started](getting-started). \ No newline at end of file +The folder is built using Eleventy to the `_site` folder, and then served by the running Gateway in the class `GatewayWebsite`. + +cURLs in markdown files are generated from the corresponding .http files using `http-client.env.json`, please keep them in sync. + +All files are treated as Liquid templates. They are processed once by Eleventy, and then _again_ by the gateway – hence the occasional use of double-encoded template tags such as `{{ '{{ origin }}' }}`. \ No newline at end of file diff --git a/doc/_includes/index-example.js b/doc/_includes/index-example.js index c82d9ea..9b5afe0 100644 --- a/doc/_includes/index-example.js +++ b/doc/_includes/index-example.js @@ -6,9 +6,9 @@ const domainId = uuid(); const meld = await clone(new MemoryLevel(), IoRemotes, { '@id': uuid(), - '@domain': `${domainId}.public.gw.m-ld.org`, + '@domain': `${domainId}.public.{{ '{{ domain }}' }}`, genesis: true, // Other clones will have `false` - io: { uri: 'https://gw.m-ld.org' } + io: { uri: "{{ '{{ origin }}' }}" } }); // Tell other clones the domain ID so they can join! diff --git a/doc/accounts.md b/doc/accounts.md index 35450eb..13adb88 100644 --- a/doc/accounts.md +++ b/doc/accounts.md @@ -2,8 +2,6 @@ layout: doc.liquid title: accounts --- -[//]: # (cURLs in this file are generated from the .http file using http-client.env.json) - # Accounts Accounts have two purposes in the **m-ld** Gateway. @@ -19,7 +17,7 @@ Account names (`≪account≫` in the below) must be composed only of **lowercas First, request an activation code with an email address. ```bash -curl -X POST --location "https://≪gateway≫/api/v1/user/≪account≫/activation" \ +curl -X POST --location "{{ '{{ origin }}' }}/api/v1/user/≪account≫/activation" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"email\": \"≪email≫\" }" @@ -32,7 +30,7 @@ An email will be sent to the given address, containing a six-digit activation co The account can then be created with another HTTP request: ```bash -curl -X POST --location "https://≪gateway≫/api/v1/user/≪account≫/key" \ +curl -X POST --location "{{ '{{ origin }}' }}/api/v1/user/≪account≫/key" \ -H "Authorization: Bearer ≪jwe≫" \ -H "X-Activation-Code: ≪emailed activation code≫" \ -H "Accept: application/json" @@ -45,7 +43,7 @@ The body of the response will be of the form `{ "auth": { "key": "≪my-key≫" The Gateway root account can be used to create any user account directly. ```bash -curl -X POST --location "https://≪gateway≫/api/v1/user/≪account name≫/key" \ +curl -X POST --location "{{ '{{ origin }}' }}/api/v1/user/≪account name≫/key" \ -H "Accept: application/json" \ --basic --user ≪root≫:≪root key≫ ``` @@ -62,7 +60,7 @@ When [connecting to subdomains](clone-subdomain), clients may need to provide au The required option can be set as follows. ```bash -curl -X PATCH --location "https://≪gateway≫/api/v1/user/≪account name≫" \ +curl -X PATCH --location "{{ '{{ origin }}' }}/api/v1/user/≪account name≫" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"@insert\": { \"remotesAuth\": \"≪remotes auth option≫\" } }" \ diff --git a/doc/clone-api.md b/doc/clone-api.md index 06c64cf..7f0335a 100644 --- a/doc/clone-api.md +++ b/doc/clone-api.md @@ -2,8 +2,6 @@ layout: doc.liquid title: clone API --- -[//]: # (cURLs in this file are generated from the .http file using http-client.env.json) - # Clone API Let's say we have an account and a named subdomain. Because we're using [named subdomains](named-subdomains), the Gateway has a clone of the information in the (sub)domain. @@ -18,7 +16,7 @@ The 'clone' (noun, not verb) API is provided for such a client to access the dat Some data can be added to the domain with: ```bash -curl -X POST --location "https://≪gateway≫/api/v1/domain/≪account name≫/≪subdomain≫/state" \ +curl -X POST --location "{{ '{{ origin }}' }}/api/v1/domain/≪account name≫/≪subdomain≫/state" \ -H "Content-Type: application/json" \ -d "{ \"@id\": \"Client-0005\", @@ -30,7 +28,7 @@ curl -X POST --location "https://≪gateway≫/api/v1/domain/≪account name≫/ Data in the domain can be queried with: ```bash -curl -X GET --location "https://≪gateway≫/api/v1/domain/≪account name≫/≪subdomain≫/state?q=%7B%22%40describe%22%3A%22%3Fid%22%2C%22%40where%22%3A%7B%22%40id%22%3A%22%3Fid%22%7D%7D" \ +curl -X GET --location "{{ '{{ origin }}' }}/api/v1/domain/≪account name≫/≪subdomain≫/state?q=%7B%22%40describe%22%3A%22%3Fid%22%2C%22%40where%22%3A%7B%22%40id%22%3A%22%3Fid%22%7D%7D" \ -H "Accept: application/json" \ --basic --user ≪account name≫:≪account key≫ ``` diff --git a/doc/getting-started.md b/doc/getting-started.md index 6e9d58a..0d7118c 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -6,19 +6,19 @@ title: getting started > I'm just getting started with **m-ld**. I want to try building an app, without having to deploy a messaging service. -We run a free public Socket.io messaging service for **m-ld**, on our cloud Gateway! Your domains will be named like `≪uuid≫.public.gw.m-ld.org`, and you'll need an internet connection to create new domains and keep clones synchronised (although individual clones can work offline, as usual). For usage instructions see [UUID subdomains](uuid-subdomains). +The Gateway serves free public messaging for **m-ld**! Your domains will be named like `≪uuid≫.public.{{ '{{ domain }}' }}`, and you'll need a network connection to create new domains and keep clones synchronised (although individual clones can work offline, as usual). For usage instructions see [UUID subdomains](uuid-subdomains). --- > I'm building a client-only browser or desktop app with **m-ld**. I want to offer secure backup of my users' data. -On our cloud Gateway you can sign up for your own free account, and create domains which are backed up there. Your domains will be named like `≪name≫.≪account≫.gw.m-ld.org`, and you'll need an internet connection to create new domains and keep clones synchronised (although individual clones can work offline, as usual). For usage instructions see [named subdomains](named-subdomains). +You can sign up for your own free Gateway account, and create domains which are backed up here. Your domains will be named like `≪name≫.≪account≫.{{ '{{ domain }}' }}`, and you'll need an internet connection to create new domains and keep clones synchronised (although individual clones can work offline, as usual). For usage instructions see [named subdomains](named-subdomains). --- > I'm building an app with a service tier, using **m-ld** for data distribution. -You can use our cloud Gateway to provide messaging and secure backup of your domains. You service tier can just talk to the cloud Gateway as if it were a client. To set your own service levels, or to work with a restricted network, you might choose to [self-host your own Gateway](self-host). +You can use the Gateway to provide messaging and secure backup of your domains. You service tier can just talk to the Gateway as if it were a client. To set your own service levels, or to work with a restricted network, you might choose to [self-host your own Gateway](self-host). --- @@ -26,6 +26,6 @@ You can use our cloud Gateway to provide messaging and secure backup of your dom Keeping a database in sync with information in **m-ld** requires a dedicated clone, local to the database, which can offer the kind of serialised updates that conventional databases like. -The best deployment approach will be to embed this local clone in your service tier, where it can be animated directly from the application logic. If an engine doesn't exist for your server platform though, you can [deploy a **m-ld** Gateway](self-host) in a "sidecar" arrangement with your services. The Gateway [clone API](clone-api) can be used to provide serialised state to your app, for synchronisation with the database. +The best deployment approach will be to embed this local clone in your service tier, where it can be animated directly from the application logic. If an engine doesn't exist for your server platform though, you can [deploy a Gateway](self-host) in a "sidecar" arrangement with your services. The Gateway [clone API](clone-api) can be used to provide serialised state to your app, for synchronisation with the database. > 🚧 More detail will be available here soon. In the meantime, please do [get in touch](http://m-ld.org/hello/) to discuss your use-case! \ No newline at end of file diff --git a/doc/http-client.env.json b/doc/http-client.env.json index 4021c65..004cb94 100644 --- a/doc/http-client.env.json +++ b/doc/http-client.env.json @@ -6,7 +6,7 @@ "subdomain": "my-subdomain" }, "doc": { - "gateway": "https://≪gateway≫", + "gateway": "{{ '{{ origin }}' }}", "rootAccount": "≪root≫", "rootKey": "≪root key≫", "account": "≪account name≫", diff --git a/doc/named-subdomains.md b/doc/named-subdomains.md index c6e0d41..8af6361 100644 --- a/doc/named-subdomains.md +++ b/doc/named-subdomains.md @@ -2,8 +2,6 @@ layout: doc.liquid title: named subdomains --- -[//]: # (cURLs in this file are generated from the .http file using http-client.env.json) - # Using Named Subdomains Named subdomains are _cloned_ in the Gateway, and are thereby backed up. @@ -15,7 +13,7 @@ To use named subdomains, you first need [an account](accounts). (If your Gateway A new domain can be created with: ```bash -curl -X PUT --location "https://≪gateway≫/api/v1/domain/≪account name≫/≪subdomain≫" \ +curl -X PUT --location "{{ '{{ origin }}' }}/api/v1/domain/≪account name≫/≪subdomain≫" \ -H "Accept: application/json" \ --basic --user ≪account name≫:≪account key≫ ``` diff --git a/doc/uuid-subdomains.md b/doc/uuid-subdomains.md index 6bea3d7..8dc14fe 100644 --- a/doc/uuid-subdomains.md +++ b/doc/uuid-subdomains.md @@ -2,9 +2,6 @@ layout: doc.liquid title: uuid subdomains --- - -[//]: # (cURLs in this file are generated from the .http file using http-client.env.json) - # Using UUID Subdomains UUID subdomains are _unmanaged_ by the Gateway, and are not backed up. The Gateway provides a message relay to allow clones on the subdomain to communicate. @@ -19,7 +16,7 @@ To use UUID subdomains, you first need [an account](accounts). By default, a Gateway account only allows [named subdomains](named-subdomains). To enable UUID subdomains for an account: ```bash -curl -X PATCH --location "https://≪gateway≫/api/v1/user/≪account name≫" \ +curl -X PATCH --location "{{ '{{ origin }}' }}/api/v1/user/≪account name≫" \ -H "Content-Type: application/json" \ -d "{ \"@insert\": { \"naming\": \"uuid\" } }" \ --basic --user ≪account name≫:≪account key≫ @@ -34,7 +31,7 @@ The domain name must take the form `≪uuid≫.my-account.my-gateway`, where ` For convenience, you can request suitable configuration for a new UUID subdomain from the Gateway, as follows. Note that this does not create anything new on the Gateway, but it will generate a compliant UUID for the domain name. ```bash -curl -X POST --location "https://≪gateway≫/api/v1/domain/≪account name≫" \ +curl -X POST --location "{{ '{{ origin }}' }}/api/v1/domain/≪account name≫" \ -H "Accept: application/json" ``` diff --git a/src/http/EndPoint.ts b/src/http/EndPoint.ts index 331b823..dce5960 100644 --- a/src/http/EndPoint.ts +++ b/src/http/EndPoint.ts @@ -44,7 +44,7 @@ function nextifyHandler(handler: NextFreeHandler): RequestHandler { }; } -type Verb = 'del' | 'get' | 'put' | 'post' | 'patch'; +type Verb = 'del' | 'get' | 'head' | 'put' | 'post' | 'patch'; type RouteDef = string | RegExp | RouteOptions; type Routable = Pick; @@ -62,6 +62,7 @@ interface EndPointSetup { export class EndPoint implements Routable { del: RestServer['del']; get: RestServer['get']; + head: RestServer['head']; put: RestServer['put']; post: RestServer['post']; patch: RestServer['patch']; @@ -79,7 +80,7 @@ export class EndPoint implements Routable { EndPoint.checkRoute(route); return `${stem}${route}`; }; - for (let verb of ['del', 'get', 'put', 'post', 'patch'] as Verb[]) { + for (let verb of ['del', 'get', 'head', 'put', 'post', 'patch'] as Verb[]) { this[verb] = (route: string, ...handlers) => outer[verb]( api(route), ...this.useHandlers, @@ -149,6 +150,7 @@ function handlerDecorator(verb: Verb | 'use', opts: RouteDef = '') { export const use = handlerDecorator('use'); export const del = handlerDecorator('del'); export const get = handlerDecorator('get'); +export const head = handlerDecorator('head'); export const put = handlerDecorator('put'); export const post = handlerDecorator('post'); export const patch = handlerDecorator('patch'); \ No newline at end of file diff --git a/src/http/GatewayWebsite.ts b/src/http/GatewayWebsite.ts index d594599..91d4a07 100644 --- a/src/http/GatewayWebsite.ts +++ b/src/http/GatewayWebsite.ts @@ -1,31 +1,45 @@ -import { EndPoint, post } from './EndPoint.js'; +import { EndPoint, get, head, post } from './EndPoint.js'; import { plugins, Request, Response, Server } from 'restify'; -import { Gateway, Notifier } from '../server/index.js'; -import { fileURLToPath } from 'url'; -import { AccountOwnedId, validate } from '../lib/index.js'; +import { AccountOwnedId, resolveGateway, validate } from '../lib/index.js'; import { as } from '../lib/validate.js'; -import { Liquid } from 'liquidjs'; import { pipeline } from 'stream/promises'; -import { toHttpError } from './errors.js'; - -/** Directory of the m-ld-gateway package */ -const siteDir = fileURLToPath(new URL('../../_site/', import.meta.url)); +import { MethodNotAllowedError, toHttpError } from './errors.js'; +import type { Gateway, Notifier } from '../server/index.js'; +import type { Liquid } from 'liquidjs'; export class GatewayWebsite extends EndPoint { - private liquid = new Liquid({ root: siteDir }); + private readonly pageVars: Promise<{ origin: string, domain: string }>; + private readonly startTime = new Date(); constructor( readonly gateway: Gateway, server: Server, - private notifier: Notifier + private readonly notifier: Notifier, + private readonly liquid: Liquid ) { super(server, '', ({ useFor }) => useFor('post', plugins.bodyParser())); - server.get('/*', plugins.serveStatic({ - directory: siteDir, default: 'index' + this.pageVars = Promise.resolve(resolveGateway(gateway.config.gateway)).then(url => ({ + origin: url.origin, domain: gateway.domainName })); } + @get('/*') + async getPage(req: Request, res: Response) { + if (req.path() === '/activate') + throw new MethodNotAllowedError; + await this.renderHtml(res, req.path().slice(1) || 'index', await this.pageVars); + } + + @head('/*') + async headPage(req: Request, res: Response) { + if (req.path() === '/activate') + throw new MethodNotAllowedError; + // This will throw ENOENT if not found + await this.liquid.parseFile(req.path().slice(1) || 'index'); + this.setHtmlHeaders(res).send(); + } + @post('/activate') async activation(req: Request, res: Response) { const { account, email, code, jwe } = req.body; @@ -47,8 +61,19 @@ export class GatewayWebsite extends EndPoint { } catch (e) { pageVars = { account, email, error: toHttpError(e).toJSON() }; } - const html = await this.liquid.renderFileToNodeStream('activate', pageVars); + await this.renderHtml(res, 'activate', pageVars); + } + + private async renderHtml(res: Response, file: string, pageVars: {}) { + // This will throw ENOENT if not found + const html = await this.liquid.renderFileToNodeStream(file, pageVars); + await pipeline(html, this.setHtmlHeaders(res)); + } + + private setHtmlHeaders(res: Response) { + res.header('content-type', 'text/html'); res.header('transfer-encoding', 'chunked'); - await pipeline(html, res); + res.header('last-modified', this.startTime); + return res; } } \ No newline at end of file diff --git a/src/http/errors.ts b/src/http/errors.ts index 0cc5c65..94b57f2 100644 --- a/src/http/errors.ts +++ b/src/http/errors.ts @@ -1,11 +1,11 @@ import errors from 'restify-errors'; import { as } from '../lib/validate.js'; -export function toHttpError(e: any) { - return e instanceof errors.HttpError ? e : - as.isError(e) ? new BadRequestError(e) : - new InternalServerError(e); -} +export const toHttpError = (e: any) => + e instanceof errors.HttpError ? e : + e?.code === 'ENOENT' ? new NotFoundError(e) : + as.isError(e) ? new BadRequestError(e) : + new InternalServerError(e); export const UnauthorizedError = errors.UnauthorizedError; export const MethodNotAllowedError = errors.MethodNotAllowedError; diff --git a/src/http/index.ts b/src/http/index.ts index 4e2053a..710d3de 100644 --- a/src/http/index.ts +++ b/src/http/index.ts @@ -1,6 +1,5 @@ import { createServer, plugins, pre, Server as RestServer } from 'restify'; import LOG from 'loglevel'; -import { Gateway, Notifier } from '../server/index.js'; import { formatter, HTML_FORMAT, JSON_LD_FORMAT } from './EndPoint.js'; import { ApiEndPoint } from './ApiEndPoint.js'; import { SubdomainEndPoint } from './SubdomainEndPoint.js'; @@ -8,8 +7,14 @@ import { SubdomainStateEndPoint } from './SubdomainStateEndPoint.js'; import { UserEndPoint } from './UserEndPoint.js'; import { DomainEndPoint } from './DomainEndPoint.js'; import { GatewayWebsite } from './GatewayWebsite.js'; +import type { Gateway, Notifier } from '../server/index.js'; +import type { Liquid } from 'liquidjs'; -export function setupGatewayHttp(gateway: Gateway, notifier: Notifier): RestServer { +export function setupGatewayHttp({ gateway, notifier, liquid }: { + gateway: Gateway, + notifier: Notifier, + liquid: Liquid +}): RestServer { const server = createServer({ formatters: { 'application/ld+json': formatter(JSON_LD_FORMAT), @@ -24,14 +29,17 @@ export function setupGatewayHttp(gateway: Gateway, notifier: Notifier): RestServ }); if (LOG.getLevel() <= LOG.levels.DEBUG) { server.pre(function (req, _res, next) { - LOG.debug(`${req.method} ${req.url} ${JSON.stringify({ - ...req.headers, authorization: undefined - })}`); + if (LOG.getLevel() <= LOG.levels.TRACE) + LOG.trace(req.method, req.url, { + ...req.headers, authorization: undefined + }); + else + LOG.debug(req.method, req.url); return next(); }); } // Set up endpoints - new GatewayWebsite(gateway, server, notifier); + new GatewayWebsite(gateway, server, notifier, liquid); const apiEndPoint = new ApiEndPoint(gateway, server); new UserEndPoint(apiEndPoint, notifier); new DomainEndPoint(apiEndPoint); diff --git a/src/lib/BaseGateway.ts b/src/lib/BaseGateway.ts index a232418..6ee4fe8 100644 --- a/src/lib/BaseGateway.ts +++ b/src/lib/BaseGateway.ts @@ -48,23 +48,22 @@ export class BaseGateway { * */ export function resolveGateway( - address: string -): { root: URL | Promise, domainName: string } { - if (isFQDN(address)) { - return { root: new URL(`https://${address}/`), domainName: address }; + address: string | URL +): URL | Promise { + if (address instanceof URL) { + return address; + } else if (isFQDN(address)) { + return new URL(`https://${address}/`); } else { const url = new URL('/', address); const domainName = url.hostname; if (domainName.endsWith('.local')) { - return { - root: dns.lookup(domainName).then(a => { - url.hostname = a.address; - return url; - }), - domainName - }; + return dns.lookup(domainName).then(a => { + url.hostname = a.address; + return url; + }); } else { - return { root: url, domainName }; + return url; } } } diff --git a/src/server/Gateway.ts b/src/server/Gateway.ts index 41827c9..bb4eddf 100644 --- a/src/server/Gateway.ts +++ b/src/server/Gateway.ts @@ -22,11 +22,10 @@ import { Subdomain, SubdomainSpec } from '../data/Subdomain.js'; export type Who = { acc: Account, keyid: string }; export class Gateway extends BaseGateway implements AccountContext { - readonly me: GatewayPrincipal; - /*readonly*/ - domain: MeldClone; + public readonly me: GatewayPrincipal; + public /*readonly*/ domain: MeldClone; + public readonly config: GatewayConfig; - private readonly config: GatewayConfig; private readonly subdomains: { [name: string]: SubdomainClone } = {}; private readonly subs: Subscription = new Subscription(); diff --git a/src/socket.io/IoCloneFactory.ts b/src/socket.io/IoCloneFactory.ts index c2ccc76..d005576 100644 --- a/src/socket.io/IoCloneFactory.ts +++ b/src/socket.io/IoCloneFactory.ts @@ -27,7 +27,7 @@ export class IoCloneFactory extends CloneFactory { this.address = address; } - remotes(config: BaseGatewayConfig) { + remotes() { return IoRemotes; } @@ -46,7 +46,7 @@ export class IoCloneFactory extends CloneFactory { ) { // Reusable config always doles out public gateway address const uri = !reusable && this.address ? this.address : - (await resolveGateway(config.gateway.toString()).root).toString(); + (await resolveGateway(config.gateway)).toString(); const io: MeldIoConfig['io'] = { uri }; // When using Socket.io, the authorisation key is sent to the server // See https://socket.io/docs/v4/middlewares/#sending-credentials diff --git a/src/start.ts b/src/start.ts index 6b70e5f..c1f1afe 100644 --- a/src/start.ts +++ b/src/start.ts @@ -7,6 +7,8 @@ import { } from './lib/index.js'; import { logNotifier, SmtpNotifier } from './server/Notifier.js'; import { uuid } from '@m-ld/m-ld'; +import { Liquid } from 'liquidjs'; +import { fileURLToPath } from 'url'; /** * @typedef {object} process.env required for Gateway node startup @@ -68,8 +70,14 @@ import { uuid } from '@m-ld/m-ld'; } const gateway = new Gateway(env, config, cloneFactory, keyStore); - const notifier = config.smtp != null ? new SmtpNotifier(config) : logNotifier; - const server = setupGatewayHttp(gateway, notifier); + const server = setupGatewayHttp({ + gateway, + notifier: config.smtp != null ? new SmtpNotifier(config) : logNotifier, + liquid: new Liquid({ + root: fileURLToPath(new URL('../_site/', import.meta.url)), + cache: true + }) + }); if (setupType === 'io') { const { IoService } = await import('./socket.io/index.js'); diff --git a/test/http.test.ts b/test/http.test.ts index d101fcb..d6d3f7b 100644 --- a/test/http.test.ts +++ b/test/http.test.ts @@ -8,11 +8,14 @@ import { setupGatewayHttp } from '../src/http/index.js'; import request from 'supertest'; import { mock, MockProxy } from 'jest-mock-extended'; import { Server } from 'restify'; +import { Readable } from 'stream'; +import type { Liquid } from 'liquidjs'; -describe('Gateway REST API', () => { +describe('Gateway HTTP API', () => { let env: TestEnv; let gateway: Gateway; let notifier: MockProxy; + let liquid: MockProxy; let cloneFactory: TestCloneFactory; let app: Server; @@ -30,7 +33,8 @@ describe('Gateway REST API', () => { }, cloneFactory, new DomainKeyStore('app')); await gateway.initialise(); notifier = mock(); - app = setupGatewayHttp(gateway, notifier); + liquid = mock(); + app = setupGatewayHttp({ gateway, notifier, liquid }); }); afterEach(async () => { @@ -38,6 +42,34 @@ describe('Gateway REST API', () => { env.tearDown(); }); + describe('website', () => { + test('head a page', async () => { + await request(app) + .head('/a') + .accept('text/html') + .expect('Content-Type', 'text/html') + .expect('Transfer-Encoding', 'chunked') + .expect(200); + expect(liquid.parseFile).toBeCalledWith('a'); + }); + + test('get index page', async () => { + liquid.renderFileToNodeStream + .mockResolvedValue(Readable.from(['html'])); + await request(app) + .get('/') + .accept('text/html') + .expect('Content-Type', 'text/html') + .expect('Transfer-Encoding', 'chunked') + .expect(200, 'html'); + expect(liquid.renderFileToNodeStream).toBeCalledWith('index', { + origin: 'https://ex.org', domain: 'ex.org' + }); + }); + + test.todo('/activate route'); + }); + test('gets context', async () => { const res = await request(app) .get('/api/v1/context')