Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions app/controllers/api/internal/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ class SessionsController < ApplicationController
respond_to :json

def show
render json: { live: live?, timeout: timeout }
render json: status_response
end

def update
analytics.session_kept_alive if live?
update_last_request_at
render json: { live: live?, timeout: timeout }
render json: status_response
end

def destroy
Expand All @@ -29,21 +29,21 @@ def destroy

private

def status_response
{ live: live?, timeout: live?.presence && timeout }
end

def skip_devise_hooks
request.env['devise.skip_timeout'] = true
request.env['devise.skip_trackable'] = true
end

def live?
timeout.future?
timeout.present? && timeout.future?
end

def timeout
if last_request_at.present?
Time.zone.at(last_request_at + User.timeout_in)
else
Time.current
end
Time.zone.at(last_request_at + User.timeout_in) if last_request_at.present?
end

def last_request_at
Expand Down
17 changes: 9 additions & 8 deletions app/javascript/packages/request/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sinon from 'sinon';
import type { SinonStub } from 'sinon';
import { useSandbox } from '@18f/identity-test-helpers';
import { request } from '.';
import { request, ResponseError } from '.';

describe('request', () => {
const sandbox = useSandbox();
Expand Down Expand Up @@ -241,13 +241,14 @@ describe('request', () => {
});

it('throws an error', async () => {
await request('https://example.com', { read: false })
.then(() => {
throw new Error('Unexpected promise resolution');
})
.catch((error) => {
expect(error).to.exist();
});
Comment on lines -244 to -250
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test wasn't effective in testing that the request will throw, since the fallback handling for "unexpected promise resolution" is what was being caught, not the error thrown by request.

let didCatch = false;
await request('https://example.com').catch((error: ResponseError) => {
expect(error).to.exist();
expect(error.status).to.equal(400);
didCatch = true;
});

expect(didCatch).to.be.true();
});

context('with read=false option', () => {
Expand Down
8 changes: 7 additions & 1 deletion app/javascript/packages/request/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
type CSRFGetter = () => string | undefined;

export class ResponseError extends Error {
status: number;
}

interface RequestOptions extends RequestInit {
/**
* Either boolean or unstringified POJO to send with the request as JSON. Defaults to true.
Expand Down Expand Up @@ -102,7 +106,9 @@ export async function request(url: string, options: Partial<RequestOptions> = {}

if (read) {
if (!response.ok) {
throw new Error();
const error = new ResponseError();
error.status = response.status;
throw error;
}

return json ? response.json() : response.text();
Expand Down
153 changes: 116 additions & 37 deletions app/javascript/packages/session/requests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,73 +7,152 @@ import {
requestSessionStatus,
extendSession,
} from './requests';
import type { SessionStatusResponse } from './requests';
import type { SessionLiveStatusResponse, SessionTimedOutStatusResponse } from './requests';

describe('requestSessionStatus', () => {
let isLive: boolean;
let timeout: string;

let server: SetupServer;
before(() => {
server = setupServer(
rest.get<{}, {}, SessionStatusResponse>(STATUS_API_ENDPOINT, (_req, res, ctx) =>
res(ctx.json({ live: isLive, timeout })),
),
);
server.listen();
});

after(() => {
server.close();
});

context('session inactive', () => {
beforeEach(() => {
isLive = false;
timeout = new Date().toISOString();
before(() => {
server = setupServer(
rest.get<{}, {}, SessionTimedOutStatusResponse>(STATUS_API_ENDPOINT, (_req, res, ctx) =>
res(ctx.json({ live: false, timeout: null })),
),
);
server.listen();
});

after(() => {
server.close();
});

it('resolves to the status', async () => {
const result = await requestSessionStatus();

expect(result).to.deep.equal({ isLive: false, timeout });
expect(result).to.deep.equal({ isLive: false });
});
});

context('session active', () => {
beforeEach(() => {
isLive = true;
let timeout: string;

before(() => {
timeout = new Date(Date.now() + 1000).toISOString();
server = setupServer(
rest.get<{}, {}, SessionLiveStatusResponse>(STATUS_API_ENDPOINT, (_req, res, ctx) =>
res(ctx.json({ live: true, timeout })),
),
);
server.listen();
});

after(() => {
server.close();
});

it('resolves to the status', async () => {
const result = await requestSessionStatus();

expect(result).to.deep.equal({ isLive: true, timeout: new Date(timeout) });
});
});

context('server responds with 401', () => {
before(() => {
server = setupServer(
rest.get<{}, {}>(STATUS_API_ENDPOINT, (_req, res, ctx) => res(ctx.status(401))),
);
server.listen();
});

after(() => {
server.close();
});

it('resolves to the status', async () => {
const result = await requestSessionStatus();

expect(result).to.deep.equal({ isLive: true, timeout });
expect(result).to.deep.equal({ isLive: false });
});
});

context('server responds with 500', () => {
before(() => {
server = setupServer(
rest.get<{}, {}>(STATUS_API_ENDPOINT, (_req, res, ctx) => res(ctx.status(500))),
);
server.listen();
});

after(() => {
server.close();
});

it('throws an error', async () => {
await expect(requestSessionStatus()).to.be.rejected();
});
});
});

describe('extendSession', () => {
const timeout = new Date(Date.now() + 1000).toISOString();

let server: SetupServer;
before(() => {
server = setupServer(
rest.post<{}, {}, SessionStatusResponse>(KEEP_ALIVE_API_ENDPOINT, (_req, res, ctx) =>
res(ctx.json({ live: true, timeout })),
),
);
server.listen();

context('session active', () => {
const timeout = new Date(Date.now() + 1000).toISOString();

before(() => {
server = setupServer(
rest.post<{}, {}, SessionLiveStatusResponse>(KEEP_ALIVE_API_ENDPOINT, (_req, res, ctx) =>
res(ctx.json({ live: true, timeout })),
),
);
server.listen();
});

after(() => {
server.close();
});

it('resolves to the status', async () => {
const result = await extendSession();

expect(result).to.deep.equal({ isLive: true, timeout: new Date(timeout) });
});
});

after(() => {
server.close();
context('server responds with 401', () => {
before(() => {
server = setupServer(
rest.post<{}, {}>(KEEP_ALIVE_API_ENDPOINT, (_req, res, ctx) => res(ctx.status(401))),
);
server.listen();
});

after(() => {
server.close();
});

it('resolves to the status', async () => {
const result = await extendSession();

expect(result).to.deep.equal({ isLive: false });
});
});

it('resolves to the status', async () => {
const result = await extendSession();
context('server responds with 500', () => {
before(() => {
server = setupServer(
rest.post<{}, {}>(KEEP_ALIVE_API_ENDPOINT, (_req, res, ctx) => res(ctx.status(500))),
);
server.listen();
});

after(() => {
server.close();
});

expect(result).to.deep.equal({ isLive: true, timeout });
it('throws an error', async () => {
await expect(extendSession()).to.be.rejected();
});
});
});
Loading