From 7a74a01b8d16cf65c5783c5a38342adbf435253f Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 17 Sep 2024 15:06:46 +0100 Subject: [PATCH 1/8] Stash: RecreateApiToken use case WIP --- .../domain/repositories/IUsersRepository.ts | 1 + src/users/domain/useCases/RecreateApiToken.ts | 19 +++++++++++ .../infra/repositories/UsersRepository.ts | 12 ++++++- .../integration/users/UsersRepository.test.ts | 33 ++++++++++--------- .../users/GetCurrentAuthenticatedUser.test.ts | 17 ++++------ 5 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 src/users/domain/useCases/RecreateApiToken.ts diff --git a/src/users/domain/repositories/IUsersRepository.ts b/src/users/domain/repositories/IUsersRepository.ts index 5549c02e..d502dc28 100644 --- a/src/users/domain/repositories/IUsersRepository.ts +++ b/src/users/domain/repositories/IUsersRepository.ts @@ -2,4 +2,5 @@ import { AuthenticatedUser } from '../models/AuthenticatedUser' export interface IUsersRepository { getCurrentAuthenticatedUser(): Promise + recreateApiToken(): Promise } diff --git a/src/users/domain/useCases/RecreateApiToken.ts b/src/users/domain/useCases/RecreateApiToken.ts new file mode 100644 index 00000000..1122ae31 --- /dev/null +++ b/src/users/domain/useCases/RecreateApiToken.ts @@ -0,0 +1,19 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IUsersRepository } from '../repositories/IUsersRepository' + +export class RecreateApiToken implements UseCase { + private usersRepository: IUsersRepository + + constructor(usersRepository: IUsersRepository) { + this.usersRepository = usersRepository + } + + /** + * Reacreates the API token of the current authenticated user and returns the new one. + * + * @returns {Promise} + */ + async execute(): Promise { + return await this.usersRepository.recreateApiToken() + } +} diff --git a/src/users/infra/repositories/UsersRepository.ts b/src/users/infra/repositories/UsersRepository.ts index a37cca8f..2f835c0d 100644 --- a/src/users/infra/repositories/UsersRepository.ts +++ b/src/users/infra/repositories/UsersRepository.ts @@ -4,14 +4,24 @@ import { AuthenticatedUser } from '../../domain/models/AuthenticatedUser' import { AxiosResponse } from 'axios' export class UsersRepository extends ApiRepository implements IUsersRepository { + private readonly usersResourceName: string = 'users' + public async getCurrentAuthenticatedUser(): Promise { - return this.doGet('/users/:me', true) + return this.doGet(`/${this.usersResourceName}/:me`, true) .then((response) => this.getAuthenticatedUserFromResponse(response)) .catch((error) => { throw error }) } + public async recreateApiToken(): Promise { + return this.doPost(`/${this.usersResourceName}/token/recreate`, {}) + .then((response) => response.data.data) + .catch((error) => { + throw error + }) + } + private getAuthenticatedUserFromResponse(response: AxiosResponse): AuthenticatedUser { const responseData = response.data.data return { diff --git a/test/integration/users/UsersRepository.test.ts b/test/integration/users/UsersRepository.test.ts index 4592482f..ea8e4db2 100644 --- a/test/integration/users/UsersRepository.test.ts +++ b/test/integration/users/UsersRepository.test.ts @@ -6,24 +6,27 @@ import { import { TestConstants } from '../../testHelpers/TestConstants' import { ReadError } from '../../../src' -describe('getCurrentAuthenticatedUser', () => { +describe('UsersRepository', () => { const sut: UsersRepository = new UsersRepository() - test('should return error when authentication is not valid', async () => { - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') + describe('getCurrentAuthenticatedUser', () => { + test('should return error when authentication is not valid', async () => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') - const errorExpected: ReadError = new ReadError('[401] Bad API key') - await expect(sut.getCurrentAuthenticatedUser()).rejects.toThrow(errorExpected) - }) - - test('should return authenticated user when valid authentication is provided', async () => { - ApiConfig.init( - TestConstants.TEST_API_URL, - DataverseApiAuthMechanism.API_KEY, - process.env.TEST_API_KEY - ) + const errorExpected: ReadError = new ReadError('[401] Bad API key') + await expect(sut.getCurrentAuthenticatedUser()).rejects.toThrow(errorExpected) + }) - const actual = await sut.getCurrentAuthenticatedUser() - expect(actual.firstName).toBe('Dataverse') + test('should return authenticated user when valid authentication is provided', async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + const actual = await sut.getCurrentAuthenticatedUser() + expect(actual.firstName).toBe('Dataverse') + }) }) + + describe('recreateApiToken', () => {}) }) diff --git a/test/unit/users/GetCurrentAuthenticatedUser.test.ts b/test/unit/users/GetCurrentAuthenticatedUser.test.ts index e0f94616..83e20d35 100644 --- a/test/unit/users/GetCurrentAuthenticatedUser.test.ts +++ b/test/unit/users/GetCurrentAuthenticatedUser.test.ts @@ -6,9 +6,10 @@ import { createAuthenticatedUser } from '../../testHelpers/users/authenticatedUs describe('execute', () => { test('should return successful result with authenticated user on repository success', async () => { const testAuthenticatedUser = createAuthenticatedUser() - const usersRepositoryStub: IUsersRepository = { - getCurrentAuthenticatedUser: jest.fn().mockReturnValue(testAuthenticatedUser) - } + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.getCurrentAuthenticatedUser = jest + .fn() + .mockResolvedValue(testAuthenticatedUser) const sut = new GetCurrentAuthenticatedUser(usersRepositoryStub) const actual = await sut.execute() @@ -17,14 +18,10 @@ describe('execute', () => { }) test('should return error result on repository error', async () => { - const usersRepositoryStub: IUsersRepository = { - getCurrentAuthenticatedUser: jest.fn().mockRejectedValue(new ReadError()) - } + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.getCurrentAuthenticatedUser = jest.fn().mockRejectedValue(new ReadError()) const sut = new GetCurrentAuthenticatedUser(usersRepositoryStub) - let actualError: ReadError = undefined - await sut.execute().catch((e) => (actualError = e)) - - expect(actualError).toBeInstanceOf(ReadError) + await expect(sut.execute()).rejects.toThrow(ReadError) }) }) From 0949578206cf935f93287b3237aec659156a3fa3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 08:53:46 +0100 Subject: [PATCH 2/8] Added: IT for recreateApiToken repository call --- .../infra/repositories/UsersRepository.ts | 2 +- .../integration/users/UsersRepository.test.ts | 27 +++++++++++++++-- test/testHelpers/users/apiTokenHelper.ts | 29 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 test/testHelpers/users/apiTokenHelper.ts diff --git a/src/users/infra/repositories/UsersRepository.ts b/src/users/infra/repositories/UsersRepository.ts index 2f835c0d..a1ee1e9e 100644 --- a/src/users/infra/repositories/UsersRepository.ts +++ b/src/users/infra/repositories/UsersRepository.ts @@ -16,7 +16,7 @@ export class UsersRepository extends ApiRepository implements IUsersRepository { public async recreateApiToken(): Promise { return this.doPost(`/${this.usersResourceName}/token/recreate`, {}) - .then((response) => response.data.data) + .then((response) => response.data.data.message.split(' ').pop()) .catch((error) => { throw error }) diff --git a/test/integration/users/UsersRepository.test.ts b/test/integration/users/UsersRepository.test.ts index ea8e4db2..1da7848b 100644 --- a/test/integration/users/UsersRepository.test.ts +++ b/test/integration/users/UsersRepository.test.ts @@ -4,11 +4,20 @@ import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' import { TestConstants } from '../../testHelpers/TestConstants' -import { ReadError } from '../../../src' +import { ReadError, WriteError } from '../../../src' +import { createApiTokenViaApi } from '../../testHelpers/users/apiTokenHelper' describe('UsersRepository', () => { const sut: UsersRepository = new UsersRepository() + afterAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + describe('getCurrentAuthenticatedUser', () => { test('should return error when authentication is not valid', async () => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') @@ -28,5 +37,19 @@ describe('UsersRepository', () => { }) }) - describe('recreateApiToken', () => {}) + describe('recreateApiToken', () => { + test('should recreate API token when valid authentication is provided', async () => { + const testApiToken = await createApiTokenViaApi() + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) + const actualRecreatedApiToken = await sut.recreateApiToken() + expect(actualRecreatedApiToken).not.toBe(testApiToken) + }) + + test('should return error when authentication is not valid', async () => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') + + const errorExpected: WriteError = new WriteError('[401] Bad API key') + await expect(sut.recreateApiToken()).rejects.toThrow(errorExpected) + }) + }) }) diff --git a/test/testHelpers/users/apiTokenHelper.ts b/test/testHelpers/users/apiTokenHelper.ts new file mode 100644 index 00000000..0b5d093e --- /dev/null +++ b/test/testHelpers/users/apiTokenHelper.ts @@ -0,0 +1,29 @@ +import axios from 'axios' +import { TestConstants } from '../TestConstants' + +const CREATE_USER_ENDPOINT = '/builtin-users?key=burrito&password=testuser' +const API_TOKEN_USER_ENDPOINT = '/builtin-users/testuser/api-token' + +export const createApiTokenViaApi = async (): Promise => { + try { + await axios.post( + `${TestConstants.TEST_API_URL}${CREATE_USER_ENDPOINT}`, + JSON.stringify({ + userName: 'testuser', + firstName: 'John', + lastName: 'Doe', + email: 'test@test.com' + }), + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + return axios + .get(`${TestConstants.TEST_API_URL}${API_TOKEN_USER_ENDPOINT}?password=testuser`) + .then((response) => response.data.data.message) + } catch (error) { + throw new Error(`Error while creating API token`) + } +} From 7db3f1a79b78491ab4deb1460ce1d6bcd31fe436 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 09:08:33 +0100 Subject: [PATCH 3/8] Added: RecreateApiToken use case tests --- src/users/index.ts | 8 +++++-- .../functional/users/RecreateApiToken.test.ts | 21 ++++++++++++++++ test/unit/users/RecreateApiToken.test.ts | 24 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/functional/users/RecreateApiToken.test.ts create mode 100644 test/unit/users/RecreateApiToken.test.ts diff --git a/src/users/index.ts b/src/users/index.ts index 26690ec6..ba50072b 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -1,7 +1,11 @@ import { UsersRepository } from './infra/repositories/UsersRepository' import { GetCurrentAuthenticatedUser } from './domain/useCases/GetCurrentAuthenticatedUser' +import { RecreateApiToken } from './domain/useCases/RecreateApiToken' -const getCurrentAuthenticatedUser = new GetCurrentAuthenticatedUser(new UsersRepository()) +const usersRepository = new UsersRepository() -export { getCurrentAuthenticatedUser } +const getCurrentAuthenticatedUser = new GetCurrentAuthenticatedUser(usersRepository) +const recreateApiToken = new RecreateApiToken(usersRepository) + +export { getCurrentAuthenticatedUser, recreateApiToken } export { AuthenticatedUser } from './domain/models/AuthenticatedUser' diff --git a/test/functional/users/RecreateApiToken.test.ts b/test/functional/users/RecreateApiToken.test.ts new file mode 100644 index 00000000..2a3ec344 --- /dev/null +++ b/test/functional/users/RecreateApiToken.test.ts @@ -0,0 +1,21 @@ +import { ApiConfig, recreateApiToken } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' +import { createApiTokenViaApi } from '../../testHelpers/users/apiTokenHelper' + +describe('execute', () => { + afterAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should successfully recreate the API token', async () => { + const testApiToken = await createApiTokenViaApi() + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) + const actualNewApiToken = await recreateApiToken.execute() + expect(actualNewApiToken).not.toBe(testApiToken) + }) +}) diff --git a/test/unit/users/RecreateApiToken.test.ts b/test/unit/users/RecreateApiToken.test.ts new file mode 100644 index 00000000..cb65da5e --- /dev/null +++ b/test/unit/users/RecreateApiToken.test.ts @@ -0,0 +1,24 @@ +import { IUsersRepository } from '../../../src/users/domain/repositories/IUsersRepository' +import { RecreateApiToken } from '../../../src/users/domain/useCases/RecreateApiToken' +import { WriteError } from '../../../src' + +describe('execute', () => { + test('should return API token on repository success', async () => { + const testNewToken = 'newToken' + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.recreateApiToken = jest.fn().mockResolvedValue(testNewToken) + const sut = new RecreateApiToken(usersRepositoryStub) + + const actual = await sut.execute() + + expect(actual).toEqual(testNewToken) + }) + + test('should return error result on repository error', async () => { + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.recreateApiToken = jest.fn().mockRejectedValue(new WriteError()) + const sut = new RecreateApiToken(usersRepositoryStub) + + await expect(sut.execute()).rejects.toThrow(WriteError) + }) +}) From 32dbe990ddcae3b67b8ad60a613129f0d1ab9404 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 20:01:53 +0100 Subject: [PATCH 4/8] Added: GetCurrentApiToken use case --- src/users/domain/models/ApiTokenInfo.ts | 4 +++ .../domain/repositories/IUsersRepository.ts | 2 ++ .../domain/useCases/GetCurrentApiToken.ts | 20 ++++++++++++++ src/users/index.ts | 4 ++- .../infra/repositories/UsersRepository.ts | 10 +++++++ .../transformers/apiTokenInfoTransformers.ts | 14 ++++++++++ test/environment/.env | 4 +-- .../users/GetCurrentApiToken.test.ts | 19 ++++++++++++++ .../functional/users/RecreateApiToken.test.ts | 8 ++++++ .../integration/users/UsersRepository.test.ts | 20 ++++++++++++++ test/unit/users/GetCurrentApiToken.test.ts | 26 +++++++++++++++++++ 11 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 src/users/domain/models/ApiTokenInfo.ts create mode 100644 src/users/domain/useCases/GetCurrentApiToken.ts create mode 100644 src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts create mode 100644 test/functional/users/GetCurrentApiToken.test.ts create mode 100644 test/unit/users/GetCurrentApiToken.test.ts diff --git a/src/users/domain/models/ApiTokenInfo.ts b/src/users/domain/models/ApiTokenInfo.ts new file mode 100644 index 00000000..0f662df9 --- /dev/null +++ b/src/users/domain/models/ApiTokenInfo.ts @@ -0,0 +1,4 @@ +export interface ApiTokenInfo { + apiToken: string + expirationDate: Date +} diff --git a/src/users/domain/repositories/IUsersRepository.ts b/src/users/domain/repositories/IUsersRepository.ts index d502dc28..c18da6d6 100644 --- a/src/users/domain/repositories/IUsersRepository.ts +++ b/src/users/domain/repositories/IUsersRepository.ts @@ -1,6 +1,8 @@ +import { ApiTokenInfo } from '../models/ApiTokenInfo' import { AuthenticatedUser } from '../models/AuthenticatedUser' export interface IUsersRepository { getCurrentAuthenticatedUser(): Promise recreateApiToken(): Promise + getCurrentApiToken(): Promise } diff --git a/src/users/domain/useCases/GetCurrentApiToken.ts b/src/users/domain/useCases/GetCurrentApiToken.ts new file mode 100644 index 00000000..f360fce0 --- /dev/null +++ b/src/users/domain/useCases/GetCurrentApiToken.ts @@ -0,0 +1,20 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ApiTokenInfo } from '../models/ApiTokenInfo' +import { IUsersRepository } from '../repositories/IUsersRepository' + +export class GetCurrentApiToken implements UseCase { + private usersRepository: IUsersRepository + + constructor(usersRepository: IUsersRepository) { + this.usersRepository = usersRepository + } + + /** + * Returns the current API token information corresponding to the current user authenticated through ApiConfig. + * + * @returns {Promise} + */ + async execute(): Promise { + return await this.usersRepository.getCurrentApiToken() + } +} diff --git a/src/users/index.ts b/src/users/index.ts index ba50072b..132615ac 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -1,11 +1,13 @@ import { UsersRepository } from './infra/repositories/UsersRepository' import { GetCurrentAuthenticatedUser } from './domain/useCases/GetCurrentAuthenticatedUser' import { RecreateApiToken } from './domain/useCases/RecreateApiToken' +import { GetCurrentApiToken } from './domain/useCases/GetCurrentApiToken' const usersRepository = new UsersRepository() const getCurrentAuthenticatedUser = new GetCurrentAuthenticatedUser(usersRepository) const recreateApiToken = new RecreateApiToken(usersRepository) +const getCurrentApiToken = new GetCurrentApiToken(usersRepository) -export { getCurrentAuthenticatedUser, recreateApiToken } +export { getCurrentAuthenticatedUser, recreateApiToken, getCurrentApiToken } export { AuthenticatedUser } from './domain/models/AuthenticatedUser' diff --git a/src/users/infra/repositories/UsersRepository.ts b/src/users/infra/repositories/UsersRepository.ts index a1ee1e9e..61aa9884 100644 --- a/src/users/infra/repositories/UsersRepository.ts +++ b/src/users/infra/repositories/UsersRepository.ts @@ -2,6 +2,8 @@ import { ApiRepository } from '../../../core/infra/repositories/ApiRepository' import { IUsersRepository } from '../../domain/repositories/IUsersRepository' import { AuthenticatedUser } from '../../domain/models/AuthenticatedUser' import { AxiosResponse } from 'axios' +import { ApiTokenInfo } from '../../domain/models/ApiTokenInfo' +import { transformApiTokenInfoResponseToApiTokenInfo } from './transformers/apiTokenInfoTransformers' export class UsersRepository extends ApiRepository implements IUsersRepository { private readonly usersResourceName: string = 'users' @@ -22,6 +24,14 @@ export class UsersRepository extends ApiRepository implements IUsersRepository { }) } + public async getCurrentApiToken(): Promise { + return this.doGet(`/${this.usersResourceName}/token`, true) + .then((response) => transformApiTokenInfoResponseToApiTokenInfo(response)) + .catch((error) => { + throw error + }) + } + private getAuthenticatedUserFromResponse(response: AxiosResponse): AuthenticatedUser { const responseData = response.data.data return { diff --git a/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts b/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts new file mode 100644 index 00000000..92d5f4fb --- /dev/null +++ b/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts @@ -0,0 +1,14 @@ +import { AxiosResponse } from 'axios' +import { ApiTokenInfo } from '../../../domain/models/ApiTokenInfo' + +export const transformApiTokenInfoResponseToApiTokenInfo = ( + response: AxiosResponse +): ApiTokenInfo => { + const apiTokenInfoPayload = response.data.data + const messageParts = apiTokenInfoPayload.message.split(' ') + const expirationDateFormattedTimestamp = `${messageParts[4]}T${messageParts[5]}` + return { + apiToken: messageParts[1], + expirationDate: new Date(expirationDateFormattedTimestamp) + } +} diff --git a/test/environment/.env b/test/environment/.env index 80e9a14e..68ddc5ed 100644 --- a/test/environment/.env +++ b/test/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.3.0 -DATAVERSE_IMAGE_REGISTRY=docker.io -DATAVERSE_IMAGE_TAG=unstable +DATAVERSE_IMAGE_REGISTRY=ghcr.io +DATAVERSE_IMAGE_TAG=10857-add-expiration-date-to-recreate-token-api DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/functional/users/GetCurrentApiToken.test.ts b/test/functional/users/GetCurrentApiToken.test.ts new file mode 100644 index 00000000..df770a6c --- /dev/null +++ b/test/functional/users/GetCurrentApiToken.test.ts @@ -0,0 +1,19 @@ +import { ApiConfig, getCurrentApiToken } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' + +describe('execute', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should return the current API token', async () => { + const actualTokenInfo = await getCurrentApiToken.execute() + expect(actualTokenInfo.apiToken).toBe(process.env.TEST_API_KEY) + expect(typeof actualTokenInfo.expirationDate).toBe('object') + }) +}) diff --git a/test/functional/users/RecreateApiToken.test.ts b/test/functional/users/RecreateApiToken.test.ts index 2a3ec344..3a0cecf1 100644 --- a/test/functional/users/RecreateApiToken.test.ts +++ b/test/functional/users/RecreateApiToken.test.ts @@ -4,6 +4,14 @@ import { TestConstants } from '../../testHelpers/TestConstants' import { createApiTokenViaApi } from '../../testHelpers/users/apiTokenHelper' describe('execute', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + afterAll(async () => { ApiConfig.init( TestConstants.TEST_API_URL, diff --git a/test/integration/users/UsersRepository.test.ts b/test/integration/users/UsersRepository.test.ts index 1da7848b..6a9f2082 100644 --- a/test/integration/users/UsersRepository.test.ts +++ b/test/integration/users/UsersRepository.test.ts @@ -52,4 +52,24 @@ describe('UsersRepository', () => { await expect(sut.recreateApiToken()).rejects.toThrow(errorExpected) }) }) + + describe('getCurrentApiToken', () => { + test('should return API token info when valid authentication is provided', async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + const actualApiTokenInfo = await sut.getCurrentApiToken() + expect(actualApiTokenInfo.apiToken).toBe(process.env.TEST_API_KEY) + expect(typeof actualApiTokenInfo.expirationDate).toBe('object') + }) + + test('should return error when authentication is not valid', async () => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') + + const errorExpected: ReadError = new ReadError('[401] Bad API key') + await expect(sut.getCurrentApiToken()).rejects.toThrow(errorExpected) + }) + }) }) diff --git a/test/unit/users/GetCurrentApiToken.test.ts b/test/unit/users/GetCurrentApiToken.test.ts new file mode 100644 index 00000000..58894313 --- /dev/null +++ b/test/unit/users/GetCurrentApiToken.test.ts @@ -0,0 +1,26 @@ +import { ReadError } from '../../../src' +import { IUsersRepository } from '../../../src/users/domain/repositories/IUsersRepository' +import { GetCurrentApiToken } from '../../../src/users/domain/useCases/GetCurrentApiToken' +import { TestConstants } from '../../testHelpers/TestConstants' + +describe('execute', () => { + test('should return API token on repository success', async () => { + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.getCurrentApiToken = jest + .fn() + .mockResolvedValue(TestConstants.TEST_DUMMY_API_KEY) + const sut = new GetCurrentApiToken(usersRepositoryStub) + + const actual = await sut.execute() + + expect(actual).toEqual(TestConstants.TEST_DUMMY_API_KEY) + }) + + test('should return error result on repository error', async () => { + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.getCurrentApiToken = jest.fn().mockRejectedValue(new ReadError()) + const sut = new GetCurrentApiToken(usersRepositoryStub) + + await expect(sut.execute()).rejects.toThrow(ReadError) + }) +}) From 38c36d2e2d046ee743b5183a27a6f98a4d05d47d Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 20:40:52 +0100 Subject: [PATCH 5/8] Changed: RecreateApiToken use case to return ApiTokenInfo object --- src/users/domain/repositories/IUsersRepository.ts | 2 +- src/users/domain/useCases/RecreateApiToken.ts | 9 +++++---- src/users/infra/repositories/UsersRepository.ts | 13 ++++++++----- .../transformers/apiTokenInfoTransformers.ts | 14 +++++++++++++- test/functional/users/RecreateApiToken.test.ts | 8 +++++--- test/integration/users/UsersRepository.test.ts | 11 +++++++---- test/testHelpers/users/apiTokenHelper.ts | 14 ++++++-------- test/unit/users/RecreateApiToken.test.ts | 11 ++++++++--- 8 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/users/domain/repositories/IUsersRepository.ts b/src/users/domain/repositories/IUsersRepository.ts index c18da6d6..68cfea23 100644 --- a/src/users/domain/repositories/IUsersRepository.ts +++ b/src/users/domain/repositories/IUsersRepository.ts @@ -3,6 +3,6 @@ import { AuthenticatedUser } from '../models/AuthenticatedUser' export interface IUsersRepository { getCurrentAuthenticatedUser(): Promise - recreateApiToken(): Promise + recreateApiToken(): Promise getCurrentApiToken(): Promise } diff --git a/src/users/domain/useCases/RecreateApiToken.ts b/src/users/domain/useCases/RecreateApiToken.ts index 1122ae31..d5b8c319 100644 --- a/src/users/domain/useCases/RecreateApiToken.ts +++ b/src/users/domain/useCases/RecreateApiToken.ts @@ -1,7 +1,8 @@ import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ApiTokenInfo } from '../models/ApiTokenInfo' import { IUsersRepository } from '../repositories/IUsersRepository' -export class RecreateApiToken implements UseCase { +export class RecreateApiToken implements UseCase { private usersRepository: IUsersRepository constructor(usersRepository: IUsersRepository) { @@ -9,11 +10,11 @@ export class RecreateApiToken implements UseCase { } /** - * Reacreates the API token of the current authenticated user and returns the new one. + * Reacreates the API token of the current authenticated user in ApiConfig and returns the new API token info. * - * @returns {Promise} + * @returns {Promise} */ - async execute(): Promise { + async execute(): Promise { return await this.usersRepository.recreateApiToken() } } diff --git a/src/users/infra/repositories/UsersRepository.ts b/src/users/infra/repositories/UsersRepository.ts index 61aa9884..a7caeec0 100644 --- a/src/users/infra/repositories/UsersRepository.ts +++ b/src/users/infra/repositories/UsersRepository.ts @@ -3,7 +3,10 @@ import { IUsersRepository } from '../../domain/repositories/IUsersRepository' import { AuthenticatedUser } from '../../domain/models/AuthenticatedUser' import { AxiosResponse } from 'axios' import { ApiTokenInfo } from '../../domain/models/ApiTokenInfo' -import { transformApiTokenInfoResponseToApiTokenInfo } from './transformers/apiTokenInfoTransformers' +import { + transformGetApiTokenResponseToApiTokenInfo, + transformRecreateApiTokenResponseToApiTokenInfo +} from './transformers/apiTokenInfoTransformers' export class UsersRepository extends ApiRepository implements IUsersRepository { private readonly usersResourceName: string = 'users' @@ -16,9 +19,9 @@ export class UsersRepository extends ApiRepository implements IUsersRepository { }) } - public async recreateApiToken(): Promise { - return this.doPost(`/${this.usersResourceName}/token/recreate`, {}) - .then((response) => response.data.data.message.split(' ').pop()) + public async recreateApiToken(): Promise { + return this.doPost(`/${this.usersResourceName}/token/recreate?returnExpiration=true`, {}) + .then((response) => transformRecreateApiTokenResponseToApiTokenInfo(response)) .catch((error) => { throw error }) @@ -26,7 +29,7 @@ export class UsersRepository extends ApiRepository implements IUsersRepository { public async getCurrentApiToken(): Promise { return this.doGet(`/${this.usersResourceName}/token`, true) - .then((response) => transformApiTokenInfoResponseToApiTokenInfo(response)) + .then((response) => transformGetApiTokenResponseToApiTokenInfo(response)) .catch((error) => { throw error }) diff --git a/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts b/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts index 92d5f4fb..1a86f6b4 100644 --- a/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts +++ b/src/users/infra/repositories/transformers/apiTokenInfoTransformers.ts @@ -1,7 +1,7 @@ import { AxiosResponse } from 'axios' import { ApiTokenInfo } from '../../../domain/models/ApiTokenInfo' -export const transformApiTokenInfoResponseToApiTokenInfo = ( +export const transformGetApiTokenResponseToApiTokenInfo = ( response: AxiosResponse ): ApiTokenInfo => { const apiTokenInfoPayload = response.data.data @@ -12,3 +12,15 @@ export const transformApiTokenInfoResponseToApiTokenInfo = ( expirationDate: new Date(expirationDateFormattedTimestamp) } } + +export const transformRecreateApiTokenResponseToApiTokenInfo = ( + response: AxiosResponse +): ApiTokenInfo => { + const apiTokenInfoPayload = response.data.data + const messageParts = apiTokenInfoPayload.message.split(' ') + const expirationDateFormattedTimestamp = `${messageParts[9]}T${messageParts[10]}` + return { + apiToken: messageParts[5], + expirationDate: new Date(expirationDateFormattedTimestamp) + } +} diff --git a/test/functional/users/RecreateApiToken.test.ts b/test/functional/users/RecreateApiToken.test.ts index 3a0cecf1..cef4f9b9 100644 --- a/test/functional/users/RecreateApiToken.test.ts +++ b/test/functional/users/RecreateApiToken.test.ts @@ -21,9 +21,11 @@ describe('execute', () => { }) test('should successfully recreate the API token', async () => { - const testApiToken = await createApiTokenViaApi() + const testApiToken = await createApiTokenViaApi('recreateApiTokenFTUser') ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) - const actualNewApiToken = await recreateApiToken.execute() - expect(actualNewApiToken).not.toBe(testApiToken) + const actualRecreatedApiTokenInfo = await recreateApiToken.execute() + expect(actualRecreatedApiTokenInfo.apiToken).not.toBeUndefined() + expect(actualRecreatedApiTokenInfo.apiToken).not.toBe(testApiToken) + expect(typeof actualRecreatedApiTokenInfo.expirationDate).toBe('object') }) }) diff --git a/test/integration/users/UsersRepository.test.ts b/test/integration/users/UsersRepository.test.ts index 6a9f2082..c0b37d99 100644 --- a/test/integration/users/UsersRepository.test.ts +++ b/test/integration/users/UsersRepository.test.ts @@ -38,11 +38,13 @@ describe('UsersRepository', () => { }) describe('recreateApiToken', () => { - test('should recreate API token when valid authentication is provided', async () => { - const testApiToken = await createApiTokenViaApi() + test('should recreate API token and return the new API token info when valid authentication is provided', async () => { + const testApiToken = await createApiTokenViaApi('recreateApiTokenITUser') ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) - const actualRecreatedApiToken = await sut.recreateApiToken() - expect(actualRecreatedApiToken).not.toBe(testApiToken) + const actualRecreatedApiTokenInfo = await sut.recreateApiToken() + expect(actualRecreatedApiTokenInfo.apiToken).not.toBeUndefined() + expect(actualRecreatedApiTokenInfo.apiToken).not.toBe(testApiToken) + expect(typeof actualRecreatedApiTokenInfo.expirationDate).toBe('object') }) test('should return error when authentication is not valid', async () => { @@ -61,6 +63,7 @@ describe('UsersRepository', () => { process.env.TEST_API_KEY ) const actualApiTokenInfo = await sut.getCurrentApiToken() + expect(actualApiTokenInfo.apiToken).not.toBeUndefined() expect(actualApiTokenInfo.apiToken).toBe(process.env.TEST_API_KEY) expect(typeof actualApiTokenInfo.expirationDate).toBe('object') }) diff --git a/test/testHelpers/users/apiTokenHelper.ts b/test/testHelpers/users/apiTokenHelper.ts index 0b5d093e..36df6729 100644 --- a/test/testHelpers/users/apiTokenHelper.ts +++ b/test/testHelpers/users/apiTokenHelper.ts @@ -1,18 +1,15 @@ import axios from 'axios' import { TestConstants } from '../TestConstants' -const CREATE_USER_ENDPOINT = '/builtin-users?key=burrito&password=testuser' -const API_TOKEN_USER_ENDPOINT = '/builtin-users/testuser/api-token' - -export const createApiTokenViaApi = async (): Promise => { +export const createApiTokenViaApi = async (userName: string): Promise => { try { await axios.post( - `${TestConstants.TEST_API_URL}${CREATE_USER_ENDPOINT}`, + `${TestConstants.TEST_API_URL}/builtin-users?key=burrito&password=${userName}`, JSON.stringify({ - userName: 'testuser', + userName: userName, firstName: 'John', lastName: 'Doe', - email: 'test@test.com' + email: `${userName}@test.com` }), { headers: { @@ -21,9 +18,10 @@ export const createApiTokenViaApi = async (): Promise => { } ) return axios - .get(`${TestConstants.TEST_API_URL}${API_TOKEN_USER_ENDPOINT}?password=testuser`) + .get(`${TestConstants.TEST_API_URL}/builtin-users/${userName}/api-token?password=${userName}`) .then((response) => response.data.data.message) } catch (error) { + console.log(error) throw new Error(`Error while creating API token`) } } diff --git a/test/unit/users/RecreateApiToken.test.ts b/test/unit/users/RecreateApiToken.test.ts index cb65da5e..39ee2c7d 100644 --- a/test/unit/users/RecreateApiToken.test.ts +++ b/test/unit/users/RecreateApiToken.test.ts @@ -1,17 +1,22 @@ import { IUsersRepository } from '../../../src/users/domain/repositories/IUsersRepository' import { RecreateApiToken } from '../../../src/users/domain/useCases/RecreateApiToken' import { WriteError } from '../../../src' +import { ApiTokenInfo } from '../../../src/users/domain/models/ApiTokenInfo' +import { TestConstants } from '../../testHelpers/TestConstants' describe('execute', () => { test('should return API token on repository success', async () => { - const testNewToken = 'newToken' + const testNewTokenInfo: ApiTokenInfo = { + apiToken: TestConstants.TEST_DUMMY_API_KEY, + expirationDate: new Date() + } const usersRepositoryStub: IUsersRepository = {} as IUsersRepository - usersRepositoryStub.recreateApiToken = jest.fn().mockResolvedValue(testNewToken) + usersRepositoryStub.recreateApiToken = jest.fn().mockResolvedValue(testNewTokenInfo) const sut = new RecreateApiToken(usersRepositoryStub) const actual = await sut.execute() - expect(actual).toEqual(testNewToken) + expect(actual).toEqual(testNewTokenInfo) }) test('should return error result on repository error', async () => { From 8bb6d322f4592c36ee5c8d260f3a8047c0876e28 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 21:00:32 +0100 Subject: [PATCH 6/8] Added: DeleteCurrentApiToken use case --- src/core/infra/repositories/ApiRepository.ts | 9 ++++++ .../domain/repositories/IUsersRepository.ts | 1 + .../domain/useCases/DeleteCurrentApiToken.ts | 19 +++++++++++ src/users/index.ts | 5 ++- .../infra/repositories/UsersRepository.ts | 8 +++++ .../users/DeleteCurrentApiToken.test.ts | 32 +++++++++++++++++++ .../integration/users/UsersRepository.test.ts | 18 +++++++++++ test/unit/users/DeleteCurrentApiToken.test.ts | 23 +++++++++++++ 8 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/users/domain/useCases/DeleteCurrentApiToken.ts create mode 100644 test/functional/users/DeleteCurrentApiToken.test.ts create mode 100644 test/unit/users/DeleteCurrentApiToken.test.ts diff --git a/src/core/infra/repositories/ApiRepository.ts b/src/core/infra/repositories/ApiRepository.ts index ff302f9d..980df121 100644 --- a/src/core/infra/repositories/ApiRepository.ts +++ b/src/core/infra/repositories/ApiRepository.ts @@ -35,6 +35,15 @@ export abstract class ApiRepository { return await this.doRequest('put', apiEndpoint, data, queryParams) } + public async doDelete(apiEndpoint: string, queryParams: object = {}): Promise { + return await axios + .delete(buildRequestUrl(apiEndpoint), buildRequestConfig(true, queryParams)) + .then((response) => response) + .catch((error) => { + throw new WriteError(this.buildErrorMessage(error)) + }) + } + protected buildApiEndpoint( resourceName: string, operation: string, diff --git a/src/users/domain/repositories/IUsersRepository.ts b/src/users/domain/repositories/IUsersRepository.ts index 68cfea23..fac0b7cb 100644 --- a/src/users/domain/repositories/IUsersRepository.ts +++ b/src/users/domain/repositories/IUsersRepository.ts @@ -5,4 +5,5 @@ export interface IUsersRepository { getCurrentAuthenticatedUser(): Promise recreateApiToken(): Promise getCurrentApiToken(): Promise + deleteCurrentApiToken(): Promise } diff --git a/src/users/domain/useCases/DeleteCurrentApiToken.ts b/src/users/domain/useCases/DeleteCurrentApiToken.ts new file mode 100644 index 00000000..00a242d1 --- /dev/null +++ b/src/users/domain/useCases/DeleteCurrentApiToken.ts @@ -0,0 +1,19 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IUsersRepository } from '../repositories/IUsersRepository' + +export class DeleteCurrentApiToken implements UseCase { + private usersRepository: IUsersRepository + + constructor(usersRepository: IUsersRepository) { + this.usersRepository = usersRepository + } + + /** + * Deletes the API token of the current authenticated user in ApiConfig. + * + * @returns {Promise} + */ + async execute(): Promise { + return await this.usersRepository.deleteCurrentApiToken() + } +} diff --git a/src/users/index.ts b/src/users/index.ts index 132615ac..7e0bd6e8 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -2,12 +2,15 @@ import { UsersRepository } from './infra/repositories/UsersRepository' import { GetCurrentAuthenticatedUser } from './domain/useCases/GetCurrentAuthenticatedUser' import { RecreateApiToken } from './domain/useCases/RecreateApiToken' import { GetCurrentApiToken } from './domain/useCases/GetCurrentApiToken' +import { DeleteCurrentApiToken } from './domain/useCases/DeleteCurrentApiToken' const usersRepository = new UsersRepository() const getCurrentAuthenticatedUser = new GetCurrentAuthenticatedUser(usersRepository) const recreateApiToken = new RecreateApiToken(usersRepository) const getCurrentApiToken = new GetCurrentApiToken(usersRepository) +const deleteCurrentApiToken = new DeleteCurrentApiToken(usersRepository) -export { getCurrentAuthenticatedUser, recreateApiToken, getCurrentApiToken } +export { getCurrentAuthenticatedUser, recreateApiToken, getCurrentApiToken, deleteCurrentApiToken } export { AuthenticatedUser } from './domain/models/AuthenticatedUser' +export { ApiTokenInfo } from './domain/models/ApiTokenInfo' diff --git a/src/users/infra/repositories/UsersRepository.ts b/src/users/infra/repositories/UsersRepository.ts index a7caeec0..4e09c514 100644 --- a/src/users/infra/repositories/UsersRepository.ts +++ b/src/users/infra/repositories/UsersRepository.ts @@ -35,6 +35,14 @@ export class UsersRepository extends ApiRepository implements IUsersRepository { }) } + public async deleteCurrentApiToken(): Promise { + return this.doDelete(`/${this.usersResourceName}/token`) + .then(() => undefined) + .catch((error) => { + throw error + }) + } + private getAuthenticatedUserFromResponse(response: AxiosResponse): AuthenticatedUser { const responseData = response.data.data return { diff --git a/test/functional/users/DeleteCurrentApiToken.test.ts b/test/functional/users/DeleteCurrentApiToken.test.ts new file mode 100644 index 00000000..85330d5d --- /dev/null +++ b/test/functional/users/DeleteCurrentApiToken.test.ts @@ -0,0 +1,32 @@ +import { ApiConfig, WriteError, deleteCurrentApiToken } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' +import { createApiTokenViaApi } from '../../testHelpers/users/apiTokenHelper' + +describe('execute', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + afterAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should successfully recreate the API token', async () => { + const testApiToken = await createApiTokenViaApi('deleteCurrentApiTokenFTUser') + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) + await deleteCurrentApiToken.execute() + // Since the token has been deleted, the next call using it should return a WriteError + await expect( + deleteCurrentApiToken.execute() + ).rejects.toBeInstanceOf(WriteError) + }) +}) diff --git a/test/integration/users/UsersRepository.test.ts b/test/integration/users/UsersRepository.test.ts index c0b37d99..95d4b9f8 100644 --- a/test/integration/users/UsersRepository.test.ts +++ b/test/integration/users/UsersRepository.test.ts @@ -75,4 +75,22 @@ describe('UsersRepository', () => { await expect(sut.getCurrentApiToken()).rejects.toThrow(errorExpected) }) }) + + describe('deleteCurrentApiToken', () => { + test('should return API token info when valid authentication is provided', async () => { + const testApiToken = await createApiTokenViaApi('deleteCurrentApiTokenITUser') + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) + await sut.deleteCurrentApiToken() + // Since the token has been deleted, the next call using it should return 401 + const errorExpected: WriteError = new WriteError('[401] Bad API key') + await expect(sut.deleteCurrentApiToken()).rejects.toThrow(errorExpected) + }) + + test('should return error when authentication is not valid', async () => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') + + const errorExpected: WriteError = new WriteError('[401] Bad API key') + await expect(sut.deleteCurrentApiToken()).rejects.toThrow(errorExpected) + }) + }) }) diff --git a/test/unit/users/DeleteCurrentApiToken.test.ts b/test/unit/users/DeleteCurrentApiToken.test.ts new file mode 100644 index 00000000..d08b8dbc --- /dev/null +++ b/test/unit/users/DeleteCurrentApiToken.test.ts @@ -0,0 +1,23 @@ +import { IUsersRepository } from '../../../src/users/domain/repositories/IUsersRepository' +import { WriteError } from '../../../src' +import { DeleteCurrentApiToken } from '../../../src/users/domain/useCases/DeleteCurrentApiToken' + +describe('execute', () => { + test('should return undefined on repository success', async () => { + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.deleteCurrentApiToken = jest.fn().mockResolvedValue(undefined) + const sut = new DeleteCurrentApiToken(usersRepositoryStub) + + const actual = await sut.execute() + + expect(actual).toBeUndefined() + }) + + test('should return error result on repository error', async () => { + const usersRepositoryStub: IUsersRepository = {} as IUsersRepository + usersRepositoryStub.deleteCurrentApiToken = jest.fn().mockRejectedValue(new WriteError()) + const sut = new DeleteCurrentApiToken(usersRepositoryStub) + + await expect(sut.execute()).rejects.toThrow(WriteError) + }) +}) From 1f3edf111405f029cf683e43e9fcf363faf45d3b Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 21:04:58 +0100 Subject: [PATCH 7/8] Changed: rename RecreateCurrentApiToken --- src/users/domain/repositories/IUsersRepository.ts | 2 +- ...RecreateApiToken.ts => RecreateCurrentApiToken.ts} | 4 ++-- src/users/index.ts | 11 ++++++++--- src/users/infra/repositories/UsersRepository.ts | 2 +- test/functional/users/DeleteCurrentApiToken.test.ts | 4 +--- ...iToken.test.ts => RecreateCurrentApiToken.test.ts} | 6 +++--- test/integration/users/UsersRepository.test.ts | 8 ++++---- ...iToken.test.ts => RecreateCurrentApiToken.test.ts} | 10 +++++----- 8 files changed, 25 insertions(+), 22 deletions(-) rename src/users/domain/useCases/{RecreateApiToken.ts => RecreateCurrentApiToken.ts} (80%) rename test/functional/users/{RecreateApiToken.test.ts => RecreateCurrentApiToken.test.ts} (80%) rename test/unit/users/{RecreateApiToken.test.ts => RecreateCurrentApiToken.test.ts} (67%) diff --git a/src/users/domain/repositories/IUsersRepository.ts b/src/users/domain/repositories/IUsersRepository.ts index fac0b7cb..2f2908e1 100644 --- a/src/users/domain/repositories/IUsersRepository.ts +++ b/src/users/domain/repositories/IUsersRepository.ts @@ -3,7 +3,7 @@ import { AuthenticatedUser } from '../models/AuthenticatedUser' export interface IUsersRepository { getCurrentAuthenticatedUser(): Promise - recreateApiToken(): Promise + recreateCurrentApiToken(): Promise getCurrentApiToken(): Promise deleteCurrentApiToken(): Promise } diff --git a/src/users/domain/useCases/RecreateApiToken.ts b/src/users/domain/useCases/RecreateCurrentApiToken.ts similarity index 80% rename from src/users/domain/useCases/RecreateApiToken.ts rename to src/users/domain/useCases/RecreateCurrentApiToken.ts index d5b8c319..bb0c7671 100644 --- a/src/users/domain/useCases/RecreateApiToken.ts +++ b/src/users/domain/useCases/RecreateCurrentApiToken.ts @@ -2,7 +2,7 @@ import { UseCase } from '../../../core/domain/useCases/UseCase' import { ApiTokenInfo } from '../models/ApiTokenInfo' import { IUsersRepository } from '../repositories/IUsersRepository' -export class RecreateApiToken implements UseCase { +export class RecreateCurrentApiToken implements UseCase { private usersRepository: IUsersRepository constructor(usersRepository: IUsersRepository) { @@ -15,6 +15,6 @@ export class RecreateApiToken implements UseCase { * @returns {Promise} */ async execute(): Promise { - return await this.usersRepository.recreateApiToken() + return await this.usersRepository.recreateCurrentApiToken() } } diff --git a/src/users/index.ts b/src/users/index.ts index 7e0bd6e8..37ae1151 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -1,16 +1,21 @@ import { UsersRepository } from './infra/repositories/UsersRepository' import { GetCurrentAuthenticatedUser } from './domain/useCases/GetCurrentAuthenticatedUser' -import { RecreateApiToken } from './domain/useCases/RecreateApiToken' +import { RecreateCurrentApiToken } from './domain/useCases/RecreateCurrentApiToken' import { GetCurrentApiToken } from './domain/useCases/GetCurrentApiToken' import { DeleteCurrentApiToken } from './domain/useCases/DeleteCurrentApiToken' const usersRepository = new UsersRepository() const getCurrentAuthenticatedUser = new GetCurrentAuthenticatedUser(usersRepository) -const recreateApiToken = new RecreateApiToken(usersRepository) +const recreateCurrentApiToken = new RecreateCurrentApiToken(usersRepository) const getCurrentApiToken = new GetCurrentApiToken(usersRepository) const deleteCurrentApiToken = new DeleteCurrentApiToken(usersRepository) -export { getCurrentAuthenticatedUser, recreateApiToken, getCurrentApiToken, deleteCurrentApiToken } +export { + getCurrentAuthenticatedUser, + recreateCurrentApiToken, + getCurrentApiToken, + deleteCurrentApiToken +} export { AuthenticatedUser } from './domain/models/AuthenticatedUser' export { ApiTokenInfo } from './domain/models/ApiTokenInfo' diff --git a/src/users/infra/repositories/UsersRepository.ts b/src/users/infra/repositories/UsersRepository.ts index 4e09c514..08eb6baa 100644 --- a/src/users/infra/repositories/UsersRepository.ts +++ b/src/users/infra/repositories/UsersRepository.ts @@ -19,7 +19,7 @@ export class UsersRepository extends ApiRepository implements IUsersRepository { }) } - public async recreateApiToken(): Promise { + public async recreateCurrentApiToken(): Promise { return this.doPost(`/${this.usersResourceName}/token/recreate?returnExpiration=true`, {}) .then((response) => transformRecreateApiTokenResponseToApiTokenInfo(response)) .catch((error) => { diff --git a/test/functional/users/DeleteCurrentApiToken.test.ts b/test/functional/users/DeleteCurrentApiToken.test.ts index 85330d5d..2cc0cc39 100644 --- a/test/functional/users/DeleteCurrentApiToken.test.ts +++ b/test/functional/users/DeleteCurrentApiToken.test.ts @@ -25,8 +25,6 @@ describe('execute', () => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) await deleteCurrentApiToken.execute() // Since the token has been deleted, the next call using it should return a WriteError - await expect( - deleteCurrentApiToken.execute() - ).rejects.toBeInstanceOf(WriteError) + await expect(deleteCurrentApiToken.execute()).rejects.toBeInstanceOf(WriteError) }) }) diff --git a/test/functional/users/RecreateApiToken.test.ts b/test/functional/users/RecreateCurrentApiToken.test.ts similarity index 80% rename from test/functional/users/RecreateApiToken.test.ts rename to test/functional/users/RecreateCurrentApiToken.test.ts index cef4f9b9..18f81581 100644 --- a/test/functional/users/RecreateApiToken.test.ts +++ b/test/functional/users/RecreateCurrentApiToken.test.ts @@ -1,4 +1,4 @@ -import { ApiConfig, recreateApiToken } from '../../../src' +import { ApiConfig, recreateCurrentApiToken } from '../../../src' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' import { TestConstants } from '../../testHelpers/TestConstants' import { createApiTokenViaApi } from '../../testHelpers/users/apiTokenHelper' @@ -21,9 +21,9 @@ describe('execute', () => { }) test('should successfully recreate the API token', async () => { - const testApiToken = await createApiTokenViaApi('recreateApiTokenFTUser') + const testApiToken = await createApiTokenViaApi('recreateCurrentApiTokenFTUser') ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) - const actualRecreatedApiTokenInfo = await recreateApiToken.execute() + const actualRecreatedApiTokenInfo = await recreateCurrentApiToken.execute() expect(actualRecreatedApiTokenInfo.apiToken).not.toBeUndefined() expect(actualRecreatedApiTokenInfo.apiToken).not.toBe(testApiToken) expect(typeof actualRecreatedApiTokenInfo.expirationDate).toBe('object') diff --git a/test/integration/users/UsersRepository.test.ts b/test/integration/users/UsersRepository.test.ts index 95d4b9f8..e51f97c4 100644 --- a/test/integration/users/UsersRepository.test.ts +++ b/test/integration/users/UsersRepository.test.ts @@ -37,11 +37,11 @@ describe('UsersRepository', () => { }) }) - describe('recreateApiToken', () => { + describe('recreateCurrentApiToken', () => { test('should recreate API token and return the new API token info when valid authentication is provided', async () => { - const testApiToken = await createApiTokenViaApi('recreateApiTokenITUser') + const testApiToken = await createApiTokenViaApi('recreateCurrentApiTokenITUser') ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) - const actualRecreatedApiTokenInfo = await sut.recreateApiToken() + const actualRecreatedApiTokenInfo = await sut.recreateCurrentApiToken() expect(actualRecreatedApiTokenInfo.apiToken).not.toBeUndefined() expect(actualRecreatedApiTokenInfo.apiToken).not.toBe(testApiToken) expect(typeof actualRecreatedApiTokenInfo.expirationDate).toBe('object') @@ -51,7 +51,7 @@ describe('UsersRepository', () => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, 'invalidApiKey') const errorExpected: WriteError = new WriteError('[401] Bad API key') - await expect(sut.recreateApiToken()).rejects.toThrow(errorExpected) + await expect(sut.recreateCurrentApiToken()).rejects.toThrow(errorExpected) }) }) diff --git a/test/unit/users/RecreateApiToken.test.ts b/test/unit/users/RecreateCurrentApiToken.test.ts similarity index 67% rename from test/unit/users/RecreateApiToken.test.ts rename to test/unit/users/RecreateCurrentApiToken.test.ts index 39ee2c7d..7b1ee7d1 100644 --- a/test/unit/users/RecreateApiToken.test.ts +++ b/test/unit/users/RecreateCurrentApiToken.test.ts @@ -1,5 +1,5 @@ import { IUsersRepository } from '../../../src/users/domain/repositories/IUsersRepository' -import { RecreateApiToken } from '../../../src/users/domain/useCases/RecreateApiToken' +import { RecreateCurrentApiToken } from '../../../src/users/domain/useCases/RecreateCurrentApiToken' import { WriteError } from '../../../src' import { ApiTokenInfo } from '../../../src/users/domain/models/ApiTokenInfo' import { TestConstants } from '../../testHelpers/TestConstants' @@ -11,8 +11,8 @@ describe('execute', () => { expirationDate: new Date() } const usersRepositoryStub: IUsersRepository = {} as IUsersRepository - usersRepositoryStub.recreateApiToken = jest.fn().mockResolvedValue(testNewTokenInfo) - const sut = new RecreateApiToken(usersRepositoryStub) + usersRepositoryStub.recreateCurrentApiToken = jest.fn().mockResolvedValue(testNewTokenInfo) + const sut = new RecreateCurrentApiToken(usersRepositoryStub) const actual = await sut.execute() @@ -21,8 +21,8 @@ describe('execute', () => { test('should return error result on repository error', async () => { const usersRepositoryStub: IUsersRepository = {} as IUsersRepository - usersRepositoryStub.recreateApiToken = jest.fn().mockRejectedValue(new WriteError()) - const sut = new RecreateApiToken(usersRepositoryStub) + usersRepositoryStub.recreateCurrentApiToken = jest.fn().mockRejectedValue(new WriteError()) + const sut = new RecreateCurrentApiToken(usersRepositoryStub) await expect(sut.execute()).rejects.toThrow(WriteError) }) From 8ac773877829fa7dfe5b72ed249a7dbcd41f6bb3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 18 Sep 2024 21:15:47 +0100 Subject: [PATCH 8/8] Added: docs for new API token management use cases --- docs/useCases.md | 64 +++++++++++++++++++ .../domain/useCases/DeleteCurrentApiToken.ts | 2 +- .../domain/useCases/GetCurrentApiToken.ts | 2 +- .../useCases/RecreateCurrentApiToken.ts | 2 +- 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 532d231a..6e88a4d0 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -52,6 +52,10 @@ The different use cases currently available in the package are classified below, - [Users](#Users) - [Users read use cases](#users-read-use-cases) - [Get Current Authenticated User](#get-current-authenticated-user) + - [Get Current API Token](#get-current-api-token) + - [Users write use cases](#users-write-use-cases) + - [Delete Current API Token](#delete-current-api-token) + - [Recreate Current API Token](#recreate-current-api-token) - [Info](#Info) - [Get Dataverse Backend Version](#get-dataverse-backend-version) - [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months) @@ -1137,6 +1141,66 @@ getCurrentAuthenticatedUser.execute().then((user: AuthenticatedUser) => { _See [use case](../src/users/domain/useCases/GetCurrentAuthenticatedUser.ts) implementation_. +### Get Current API Token + +Returns the current [ApiTokenInfo](../src/users/domain/models/ApiTokenInfo.ts) corresponding to the current authenticated user. + +##### Example call: + +```typescript +import { getCurrentApiToken } from '@iqss/dataverse-client-javascript' + +/* ... */ + +getCurrentApiToken.execute().then((apiTokenInfo: ApiTokenInfo) => { + /* ... */ +}) + +/* ... */ +``` + +_See [use case](../src/users/domain/useCases/GetCurrentApiToken.ts) implementation_. + +### Users write use cases + +### Delete Current API Token + +Deletes the API token of the current authenticated user. + +##### Example call: + +```typescript +import { deleteCurrentApiToken } from '@iqss/dataverse-client-javascript' + +/* ... */ + +deleteCurrentApiToken.execute() + +/* ... */ +``` + +_See [use case](../src/users/domain/useCases/DeleteCurrentApiToken.ts) implementation_. + +### Recreate Current API Token + +Reacreates the API token of the current authenticated user and returns the new [ApiTokenInfo](../src/users/domain/models/ApiTokenInfo.ts). + +##### Example call: + +```typescript +import { recreateCurrentApiToken } from '@iqss/dataverse-client-javascript' + +/* ... */ + +recreateCurrentApiToken.execute().then((apiTokenInfo: ApiTokenInfo) => { + /* ... */ +}) + +/* ... */ +``` + +_See [use case](../src/users/domain/useCases/RecreateCurrentApiToken.ts) implementation_. + ## Info #### Get Dataverse Backend Version diff --git a/src/users/domain/useCases/DeleteCurrentApiToken.ts b/src/users/domain/useCases/DeleteCurrentApiToken.ts index 00a242d1..d1a5f4c0 100644 --- a/src/users/domain/useCases/DeleteCurrentApiToken.ts +++ b/src/users/domain/useCases/DeleteCurrentApiToken.ts @@ -9,7 +9,7 @@ export class DeleteCurrentApiToken implements UseCase { } /** - * Deletes the API token of the current authenticated user in ApiConfig. + * Deletes the API token of the current authenticated user. * * @returns {Promise} */ diff --git a/src/users/domain/useCases/GetCurrentApiToken.ts b/src/users/domain/useCases/GetCurrentApiToken.ts index f360fce0..faa8215b 100644 --- a/src/users/domain/useCases/GetCurrentApiToken.ts +++ b/src/users/domain/useCases/GetCurrentApiToken.ts @@ -10,7 +10,7 @@ export class GetCurrentApiToken implements UseCase { } /** - * Returns the current API token information corresponding to the current user authenticated through ApiConfig. + * Returns the current ApiTokenInfo corresponding to the current authenticated user. * * @returns {Promise} */ diff --git a/src/users/domain/useCases/RecreateCurrentApiToken.ts b/src/users/domain/useCases/RecreateCurrentApiToken.ts index bb0c7671..01a8ba10 100644 --- a/src/users/domain/useCases/RecreateCurrentApiToken.ts +++ b/src/users/domain/useCases/RecreateCurrentApiToken.ts @@ -10,7 +10,7 @@ export class RecreateCurrentApiToken implements UseCase { } /** - * Reacreates the API token of the current authenticated user in ApiConfig and returns the new API token info. + * Reacreates the API token of the current authenticated user and returns the new ApiTokenInfo. * * @returns {Promise} */