Skip to content

Commit c3809f2

Browse files
authored
Merge pull request #171 from flowcore-io/add-init-new-user-command
feat: add user initialization command for Keycloak with corresponding…
2 parents 3fa45e1 + 722c822 commit c3809f2

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

src/commands/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ export * from "./security/pat.get.ts"
8585
export * from "./security/pat.list.ts"
8686
export * from "./security/permissions.list.ts"
8787

88+
// User
89+
export * from "./user/user.initialize-in-keycloak.ts"
90+
8891
// Scenario
8992
export * from "./scenario/scenario.create.ts"
9093
export * from "./scenario/scenario.delete.ts"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { GraphQlCommand } from "../../common/command-graphql.ts"
2+
import { InvalidResponseException } from "../../exceptions/invalid-response.ts"
3+
4+
/**
5+
* The input for initializing user in Keycloak
6+
* No input parameters needed - checks current authenticated user
7+
*/
8+
export type UserInitializeInKeycloakInput = Record<PropertyKey, never>
9+
10+
/**
11+
* The output for initializing user in Keycloak
12+
*/
13+
export interface UserInitializeInKeycloakOutput {
14+
/** Whether the user is initialized */
15+
isInitialized: boolean
16+
/** User data if available */
17+
me?: unknown
18+
}
19+
20+
const QUERY = `
21+
query UserIsInitializedIfDoesNotExist {
22+
me
23+
}
24+
`
25+
26+
/**
27+
* Initialize user in Keycloak - checks if user exists and is initialized
28+
*/
29+
export class UserInitializeInKeycloakCommand extends GraphQlCommand<
30+
UserInitializeInKeycloakInput,
31+
UserInitializeInKeycloakOutput
32+
> {
33+
/**
34+
* Get the body for the request
35+
*/
36+
protected override getBody(): Record<string, unknown> {
37+
return {
38+
query: QUERY,
39+
variables: {},
40+
}
41+
}
42+
43+
/**
44+
* Parse the response
45+
*/
46+
protected override parseResponse(rawResponse: unknown): UserInitializeInKeycloakOutput {
47+
const response = rawResponse as {
48+
data?: {
49+
me?: unknown
50+
}
51+
errors?: Array<{ message: string }>
52+
}
53+
54+
if (response.errors && response.errors.length > 0) {
55+
throw new InvalidResponseException(
56+
response.errors.map((e) => e.message).join(", "),
57+
{ graphql: response.errors.map((e) => e.message).join(", ") },
58+
)
59+
}
60+
61+
// If me exists and is not null i.e. flowcore_user_id is not null, user is initialized
62+
const isInitialized = response.data?.me !== null && response.data?.me !== undefined
63+
64+
return {
65+
isInitialized,
66+
me: response.data?.me,
67+
}
68+
}
69+
}

test/tests/commands/user.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { assertEquals } from "@std/assert"
2+
import { afterAll, afterEach, describe, it } from "@std/testing/bdd"
3+
import { FlowcoreClient, UserInitializeInKeycloakCommand } from "../../../src/mod.ts"
4+
import { FetchMocker } from "../../fixtures/fetch.fixture.ts"
5+
6+
describe("User", () => {
7+
const fetchMocker = new FetchMocker()
8+
const flowcoreClient = new FlowcoreClient({ getBearerToken: () => "BEARER_TOKEN" })
9+
const fetchMockerBuilder = fetchMocker.mock("https://graph.api.flowcore.io")
10+
11+
afterEach(() => {
12+
fetchMocker.assert()
13+
})
14+
afterAll(() => {
15+
fetchMocker.restore()
16+
})
17+
18+
describe("UserInitializeInKeycloakCommand", () => {
19+
it("should return initialized true when user exists", async () => {
20+
// arrange
21+
const userData = { id: "user123", email: "[email protected]" }
22+
23+
fetchMockerBuilder.post("/graphql")
24+
.matchBody({
25+
query: `
26+
query UserIsInitializedIfDoesNotExist {
27+
me
28+
}
29+
`,
30+
variables: {},
31+
})
32+
.respondWith(200, {
33+
data: {
34+
me: userData,
35+
},
36+
})
37+
38+
// act
39+
const command = new UserInitializeInKeycloakCommand({})
40+
const response = await flowcoreClient.execute(command)
41+
42+
// assert
43+
assertEquals(response.isInitialized, true)
44+
assertEquals(response.me, userData)
45+
})
46+
47+
it("should return initialized false when user does not exist", async () => {
48+
// arrange
49+
fetchMockerBuilder.post("/graphql")
50+
.matchBody({
51+
query: `
52+
query UserIsInitializedIfDoesNotExist {
53+
me
54+
}
55+
`,
56+
variables: {},
57+
})
58+
.respondWith(200, {
59+
data: {
60+
me: null,
61+
},
62+
})
63+
64+
// act
65+
const command = new UserInitializeInKeycloakCommand({})
66+
const response = await flowcoreClient.execute(command)
67+
68+
// assert
69+
assertEquals(response.isInitialized, false)
70+
assertEquals(response.me, null)
71+
})
72+
73+
it("should handle undefined me response", async () => {
74+
// arrange
75+
fetchMockerBuilder.post("/graphql")
76+
.matchBody({
77+
query: `
78+
query UserIsInitializedIfDoesNotExist {
79+
me
80+
}
81+
`,
82+
variables: {},
83+
})
84+
.respondWith(200, {
85+
data: {},
86+
})
87+
88+
// act
89+
const command = new UserInitializeInKeycloakCommand({})
90+
const response = await flowcoreClient.execute(command)
91+
92+
// assert
93+
assertEquals(response.isInitialized, false)
94+
assertEquals(response.me, undefined)
95+
})
96+
})
97+
})

0 commit comments

Comments
 (0)