Skip to content

Commit

Permalink
feat: log out using OIDC
Browse files Browse the repository at this point in the history
  • Loading branch information
huwshimi committed Jun 18, 2024
1 parent 592b3c4 commit 0a91419
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
59 changes: 58 additions & 1 deletion src/store/app/thunks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import {
jujuStateFactory,
} from "testing/factories/juju/juju";

import { logOut, connectAndStartPolling } from "./thunks";
import { logOut, connectAndStartPolling, Label } from "./thunks";

describe("thunks", () => {
const consoleError = console.error;
let state: RootState;

beforeEach(() => {
console.error = vi.fn();
fetchMock.resetMocks();
state = rootStateFactory.build({
general: generalStateFactory.build({
config: configFactory.build({
Expand Down Expand Up @@ -85,6 +86,62 @@ describe("thunks", () => {
expect(dispatchedThunk.type).toBe("app/connectAndStartPolling/fulfilled");
});

it("logOut from OIDC", async () => {
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 200 });
const action = logOut();
const dispatch = vi.fn();
const getState = vi.fn(() =>
rootStateFactory.build({
general: generalStateFactory.build({
config: configFactory.build({
authMethod: AuthMethod.OIDC,
}),
}),
}),
);
await action(dispatch, getState, null);
expect(dispatch).toHaveBeenCalledWith(jujuActions.clearModelData());
expect(dispatch).toHaveBeenCalledWith(jujuActions.clearControllerData());
expect(dispatch).toHaveBeenCalledWith(generalActions.logOut());
const dispatchedThunk = await dispatch.mock.calls[4][0](
dispatch,
getState,
null,
);
expect(dispatchedThunk.type).toBe("jimm/logout/fulfilled");
});

it("handles OIDC log out errors", async () => {
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 400 });
const action = logOut();
const dispatch = vi.fn().mockImplementation((action) => {
if (typeof action === "function") {
// This is a thunk so the action name is not accessible, so this just
// throws on the first thunk that is dispatched. If this test is
// failing then check if another thunk is being dispatched before the
// logout() thunk.
return { type: "jimm/logout/rejected", error: "Uh oh" };
}
return action;
});
const getState = vi.fn(() =>
rootStateFactory.build({
general: generalStateFactory.build({
config: configFactory.build({
authMethod: AuthMethod.OIDC,
}),
}),
}),
);
await action(dispatch, getState, null);
expect(dispatch).toHaveBeenCalledWith(jujuActions.clearModelData());
expect(dispatch).toHaveBeenCalledWith(jujuActions.clearControllerData());
expect(dispatch).toHaveBeenCalledWith(generalActions.logOut());
expect(dispatch).toHaveBeenCalledWith(
generalActions.storeConnectionError(Label.OIDC_LOGOUT_ERROR),
);
});

it("connectAndStartPolling", async () => {
const dispatch = vi.fn();
const getState = vi.fn(() => state);
Expand Down
12 changes: 12 additions & 0 deletions src/store/app/thunks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createAsyncThunk } from "@reduxjs/toolkit";

import bakery from "juju/bakery";
import { logout } from "juju/jimm/thunks";
import { actions as appActions } from "store/app";
import { actions as generalActions } from "store/general";
import {
Expand All @@ -16,6 +17,10 @@ import type { RootState } from "store/store";

import type { ControllerArgs } from "./actions";

export enum Label {
OIDC_LOGOUT_ERROR = "Unable to log out.",
}

export const logOut = createAsyncThunk<
void,
void,
Expand All @@ -38,6 +43,13 @@ export const logOut = createAsyncThunk<
// to the controller to get another wait url and start polling on it
// again.
await thunkAPI.dispatch(connectAndStartPolling());
} else if (authMethod === AuthMethod.OIDC) {
const response = await thunkAPI.dispatch(logout());
if ("error" in response) {
thunkAPI.dispatch(
generalActions.storeConnectionError(Label.OIDC_LOGOUT_ERROR),
);
}
}
});

Expand Down

0 comments on commit 0a91419

Please sign in to comment.