-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #123 from storyblok/feature/user-cmd
feat: user cmd
- Loading branch information
Showing
11 changed files
with
237 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
name: Run linters | ||
name: Run Tests | ||
on: [push] | ||
|
||
env: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import chalk from 'chalk' | ||
import { getUser } from './actions' | ||
import { http, HttpResponse } from 'msw' | ||
import { setupServer } from 'msw/node' | ||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' | ||
|
||
const handlers = [ | ||
http.get('https://api.storyblok.com/v1/users/me', async ({ request }) => { | ||
const token = request.headers.get('Authorization') | ||
if (token === 'valid-token') { | ||
return HttpResponse.json({ data: 'user data' }) | ||
} | ||
return new HttpResponse('Unauthorized', { status: 401 }) | ||
}), | ||
] | ||
|
||
const server = setupServer(...handlers) | ||
|
||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) | ||
|
||
afterEach(() => server.resetHandlers()) | ||
afterAll(() => server.close()) | ||
|
||
describe('user actions', () => { | ||
beforeEach(() => { | ||
vi.clearAllMocks() | ||
}) | ||
|
||
describe('getUser', () => { | ||
it('should get user successfully with a valid token', async () => { | ||
const mockResponse = { data: 'user data' } | ||
const result = await getUser('valid-token', 'eu') | ||
expect(result).toEqual(mockResponse) | ||
}) | ||
}) | ||
|
||
it('should throw an masked error for invalid token', async () => { | ||
await expect(getUser('invalid-token', 'eu')).rejects.toThrow( | ||
new Error(`The token provided ${chalk.bold('inva*********')} is invalid. | ||
Please make sure you are using the correct token and try again.`), | ||
) | ||
}) | ||
|
||
it('should throw a network error if response is empty (network)', async () => { | ||
server.use( | ||
http.get('https://api.storyblok.com/v1/users/me', () => { | ||
return new HttpResponse(null, { status: 500 }) | ||
}), | ||
) | ||
await expect(getUser('any-token', 'eu')).rejects.toThrow( | ||
'No response from server, please check if you are correctly connected to internet', | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { FetchError, ofetch } from 'ofetch' | ||
import { regionsDomain } from '../../constants' | ||
import chalk from 'chalk' | ||
import { APIError, maskToken } from '../../utils' | ||
|
||
export const getUser = async (token: string, region: string) => { | ||
try { | ||
return await ofetch(`https://${regionsDomain[region]}/v1/users/me`, { | ||
headers: { | ||
Authorization: token, | ||
}, | ||
}) | ||
} | ||
catch (error) { | ||
if (error instanceof FetchError) { | ||
const status = error.response?.status | ||
|
||
switch (status) { | ||
case 401: | ||
throw new APIError('unauthorized', 'get_user', error, `The token provided ${chalk.bold(maskToken(token))} is invalid. | ||
Please make sure you are using the correct token and try again.`) | ||
default: | ||
throw new APIError('network_error', 'get_user', error) | ||
} | ||
} | ||
else { | ||
throw new APIError('generic', 'get_user', error as Error) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { userCommand } from './' | ||
import { getUser } from './actions' | ||
import { konsola } from '../../utils' | ||
import { session } from '../../session' | ||
import chalk from 'chalk' | ||
|
||
vi.mock('./actions', () => ({ | ||
getUser: vi.fn(), | ||
})) | ||
|
||
vi.mock('../../creds', () => ({ | ||
isAuthorized: vi.fn(), | ||
})) | ||
|
||
// Mocking the session module | ||
vi.mock('../../session', () => { | ||
let _cache | ||
const session = () => { | ||
if (!_cache) { | ||
_cache = { | ||
state: { | ||
isLoggedIn: false, | ||
}, | ||
updateSession: vi.fn(), | ||
persistCredentials: vi.fn(), | ||
initializeSession: vi.fn(), | ||
} | ||
} | ||
return _cache | ||
} | ||
|
||
return { | ||
session, | ||
} | ||
}) | ||
|
||
vi.mock('../../utils', async () => { | ||
const actualUtils = await vi.importActual('../../utils') | ||
return { | ||
...actualUtils, | ||
konsola: { | ||
ok: vi.fn(), | ||
title: vi.fn(), | ||
error: vi.fn(), | ||
}, | ||
handleError: (error: Error, header = false) => { | ||
konsola.error(error, header) | ||
// Optionally, prevent process.exit during tests | ||
}, | ||
} | ||
}) | ||
|
||
describe('userCommand', () => { | ||
beforeEach(() => { | ||
vi.resetAllMocks() | ||
vi.clearAllMocks() | ||
}) | ||
|
||
it('should show the user information', async () => { | ||
const mockResponse = { | ||
user: { | ||
friendly_name: 'John Doe', | ||
email: '[email protected]', | ||
}, | ||
} | ||
session().state = { | ||
isLoggedIn: true, | ||
password: 'valid-token', | ||
region: 'eu', | ||
} | ||
getUser.mockResolvedValue(mockResponse) | ||
await userCommand.parseAsync(['node', 'test']) | ||
|
||
expect(getUser).toHaveBeenCalledWith('valid-token', 'eu') | ||
expect(konsola.ok).toHaveBeenCalledWith( | ||
`Hi ${chalk.bold('John Doe')}, you are currently logged in with ${chalk.hex('#45bfb9')(mockResponse.user.email)} on ${chalk.bold('eu')} region`, | ||
) | ||
}) | ||
|
||
it('should show an error if the user is not logged in', async () => { | ||
session().state = { | ||
isLoggedIn: false, | ||
} | ||
await userCommand.parseAsync(['node', 'test']) | ||
|
||
expect(konsola.error).toHaveBeenCalledWith(new Error(`You are currently not logged in. Please login first to get your user info.`), false) | ||
}) | ||
|
||
it('should show an error if the user information cannot be fetched', async () => { | ||
session().state = { | ||
isLoggedIn: true, | ||
password: 'valid-token', | ||
region: 'eu', | ||
} | ||
|
||
const mockError = new Error('Network error') | ||
|
||
getUser.mockRejectedValue(mockError) | ||
|
||
await userCommand.parseAsync(['node', 'test']) | ||
|
||
expect(konsola.error).toHaveBeenCalledWith(mockError, true) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import chalk from 'chalk' | ||
import type { NetrcMachine } from '../../creds' | ||
import { colorPalette, commands } from '../../constants' | ||
import { getProgram } from '../../program' | ||
import { CommandError, handleError, konsola } from '../../utils' | ||
import { getUser } from './actions' | ||
import { session } from '../../session' | ||
|
||
const program = getProgram() // Get the shared singleton instance | ||
|
||
export const userCommand = program | ||
.command(commands.USER) | ||
.description('Get the current user') | ||
.action(async () => { | ||
konsola.title(` ${commands.USER} `, colorPalette.USER) | ||
const { state, initializeSession } = session() | ||
await initializeSession() | ||
|
||
if (!state.isLoggedIn) { | ||
handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`)) | ||
return | ||
} | ||
try { | ||
const { password, region } = state as NetrcMachine | ||
const { user } = await getUser(password, region) | ||
konsola.ok(`Hi ${chalk.bold(user.friendly_name)}, you are currently logged in with ${chalk.hex(colorPalette.PRIMARY)(user.email)} on ${chalk.bold(region)} region`) | ||
} | ||
catch (error) { | ||
handleError(error as Error, true) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters