Skip to content

Commit bd22efa

Browse files
committed
chore(shared-ini-file-loader): add utility loadSsoSessions (#132)
1 parent 3472317 commit bd22efa

File tree

5 files changed

+153
-0
lines changed

5 files changed

+153
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { getSsoSessionData } from "./getSsoSessionData";
2+
3+
describe(getSsoSessionData.name, () => {
4+
it("returns empty for no data", () => {
5+
expect(getSsoSessionData({})).toStrictEqual({});
6+
});
7+
8+
it("skips sections without prefix sso-session", () => {
9+
const mockInput = { test: { key: "value" } };
10+
expect(getSsoSessionData(mockInput)).toStrictEqual({});
11+
});
12+
13+
it("skips sections with different prefix", () => {
14+
const mockInput = { "not-sso-session test": { key: "value" } };
15+
expect(getSsoSessionData(mockInput)).toStrictEqual({});
16+
});
17+
18+
describe("normalizes sso-session names", () => {
19+
const getMockSsoSessionData = (ssoSessionName: string) =>
20+
[1, 2, 3]
21+
.map((num) => [`key_${ssoSessionName}_${num}`, `value_${ssoSessionName}_${num}`])
22+
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
23+
24+
const getMockOutput = (ssoSessionNames: string[]) =>
25+
ssoSessionNames.reduce((acc, profileName) => ({ ...acc, [profileName]: getMockSsoSessionData(profileName) }), {});
26+
27+
const getMockInput = (mockOutput: { [key: string]: { [key: string]: string } }) =>
28+
Object.entries(mockOutput).reduce((acc, [key, value]) => ({ ...acc, [`sso-session ${key}`]: value }), {});
29+
30+
it("single sso-session section", () => {
31+
const mockOutput = getMockOutput(["one"]);
32+
const mockInput = getMockInput(mockOutput);
33+
expect(getSsoSessionData(mockInput)).toStrictEqual(mockOutput);
34+
});
35+
36+
it("two sso-session sections", () => {
37+
const mockOutput = getMockOutput(["one", "two"]);
38+
const mockInput = getMockInput(mockOutput);
39+
expect(getSsoSessionData(mockInput)).toStrictEqual(mockOutput);
40+
});
41+
42+
it("three sso-session sections", () => {
43+
const mockOutput = getMockOutput(["one", "two", "three"]);
44+
const mockInput = getMockInput(mockOutput);
45+
expect(getSsoSessionData(mockInput)).toStrictEqual(mockOutput);
46+
});
47+
48+
it("with section without prefix", () => {
49+
const sectionWithoutPrefix = { test: { key: "value" } };
50+
const mockOutput = getMockOutput(["one"]);
51+
const mockInput = getMockInput(mockOutput);
52+
expect(getSsoSessionData({ ...sectionWithoutPrefix, ...mockInput })).toStrictEqual(mockOutput);
53+
});
54+
});
55+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ParsedIniData } from "@aws-sdk/types";
2+
3+
const ssoSessionKeyRegex = /^sso-session\s(["'])?([^\1]+)\1$/;
4+
5+
/**
6+
* Returns the sso-session data from parsed ini data by reading
7+
* ssoSessionName after sso-session prefix including/excluding quotes
8+
*/
9+
export const getSsoSessionData = (data: ParsedIniData): ParsedIniData =>
10+
Object.entries(data)
11+
// filter out non sso-session keys
12+
.filter(([key]) => ssoSessionKeyRegex.test(key))
13+
// replace sso-session key with sso-session name
14+
.reduce((acc, [key, value]) => ({ ...acc, [ssoSessionKeyRegex.exec(key)![2]]: value }), {});

packages/shared-ini-file-loader/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ export * from "./getProfileName";
33
export * from "./getSSOTokenFilepath";
44
export * from "./getSSOTokenFromFile";
55
export * from "./loadSharedConfigFiles";
6+
export * from "./loadSsoSessionData";
67
export * from "./parseKnownFiles";
78
export * from "./types";
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { getConfigFilepath } from "./getConfigFilepath";
2+
import { getSsoSessionData } from "./getSsoSessionData";
3+
import { loadSsoSessionData } from "./loadSsoSessionData";
4+
import { parseIni } from "./parseIni";
5+
import { slurpFile } from "./slurpFile";
6+
7+
jest.mock("./getConfigFilepath");
8+
jest.mock("./getSsoSessionData");
9+
jest.mock("./parseIni");
10+
jest.mock("./slurpFile");
11+
12+
describe(loadSsoSessionData.name, () => {
13+
const mockConfigFilepath = "/mock/file/path/config";
14+
const mockSsoSessionData = { test: { key: "value" } };
15+
16+
beforeEach(() => {
17+
(getConfigFilepath as jest.Mock).mockReturnValue(mockConfigFilepath);
18+
(parseIni as jest.Mock).mockImplementation((args) => args);
19+
(getSsoSessionData as jest.Mock).mockReturnValue(mockSsoSessionData);
20+
(slurpFile as jest.Mock).mockImplementation((path) => Promise.resolve(path));
21+
});
22+
23+
afterEach(() => {
24+
jest.clearAllMocks();
25+
});
26+
27+
it("returns configFile from default locations", async () => {
28+
const ssoSessionData = await loadSsoSessionData();
29+
expect(ssoSessionData).toStrictEqual(mockSsoSessionData);
30+
expect(getConfigFilepath).toHaveBeenCalledWith();
31+
});
32+
33+
it("returns configFile from init if defined", async () => {
34+
const ssoSessionData = await loadSsoSessionData({
35+
configFilepath: mockConfigFilepath,
36+
});
37+
expect(ssoSessionData).toStrictEqual(mockSsoSessionData);
38+
expect(getConfigFilepath).not.toHaveBeenCalled();
39+
});
40+
41+
describe("swallows error and returns empty configuration", () => {
42+
it("when readFile throws error", async () => {
43+
(slurpFile as jest.Mock).mockRejectedValue("error");
44+
const ssoSessionData = await loadSsoSessionData();
45+
expect(ssoSessionData).toStrictEqual({});
46+
});
47+
48+
it("when parseIni throws error", async () => {
49+
(parseIni as jest.Mock).mockRejectedValue("error");
50+
const ssoSessionData = await loadSsoSessionData();
51+
expect(ssoSessionData).toStrictEqual({});
52+
});
53+
54+
it("when normalizeConfigFile throws error", async () => {
55+
(getSsoSessionData as jest.Mock).mockRejectedValue("error");
56+
const ssoSessionData = await loadSsoSessionData();
57+
expect(ssoSessionData).toStrictEqual({});
58+
});
59+
});
60+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ParsedIniData } from "@aws-sdk/types";
2+
3+
import { getConfigFilepath } from "./getConfigFilepath";
4+
import { getSsoSessionData } from "./getSsoSessionData";
5+
import { parseIni } from "./parseIni";
6+
import { slurpFile } from "./slurpFile";
7+
8+
export interface SsoSessionInit {
9+
/**
10+
* The path at which to locate the ini config file. Defaults to the value of
11+
* the `AWS_CONFIG_FILE` environment variable (if defined) or
12+
* `~/.aws/config` otherwise.
13+
*/
14+
configFilepath?: string;
15+
}
16+
17+
const swallowError = () => ({});
18+
19+
export const loadSsoSessionData = async (init: SsoSessionInit = {}): Promise<ParsedIniData> =>
20+
slurpFile(init.configFilepath ?? getConfigFilepath())
21+
.then(parseIni)
22+
.then(getSsoSessionData)
23+
.catch(swallowError);

0 commit comments

Comments
 (0)