From 4ce6b26294410e20d278ddf0fca8d56362b7be70 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 4 Sep 2025 09:58:46 -0400 Subject: [PATCH 1/2] feat: new use case for licenses --- docs/useCases.md | 22 +++++++ src/index.ts | 1 + src/licenses/domain/models/License.ts | 8 +++ .../repositories/ILicensesRepository.ts | 5 ++ .../transformers/LicensePayload.ts | 14 +++++ .../transformers/licenseTransformers.ts | 15 +++++ .../useCases/GetAvailableStandardLicenses.ts | 20 +++++++ src/licenses/index.ts | 10 ++++ .../infra/repositories/LicensesRepository.ts | 15 +++++ .../GetAvailableStandardLicenses.test.ts | 51 +++++++++++++++++ .../licenses/LicensesRepository.test.ts | 57 +++++++++++++++++++ .../GetAvailableStandardLicenses.test.ts | 52 +++++++++++++++++ 12 files changed, 270 insertions(+) create mode 100644 src/licenses/domain/models/License.ts create mode 100644 src/licenses/domain/repositories/ILicensesRepository.ts create mode 100644 src/licenses/domain/repositories/transformers/LicensePayload.ts create mode 100644 src/licenses/domain/repositories/transformers/licenseTransformers.ts create mode 100644 src/licenses/domain/useCases/GetAvailableStandardLicenses.ts create mode 100644 src/licenses/index.ts create mode 100644 src/licenses/infra/repositories/LicensesRepository.ts create mode 100644 test/functional/licenses/GetAvailableStandardLicenses.test.ts create mode 100644 test/integration/licenses/LicensesRepository.test.ts create mode 100644 test/unit/licenses/GetAvailableStandardLicenses.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 773c9122..ff7a266a 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -89,6 +89,8 @@ The different use cases currently available in the package are classified below, - [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months) - [Get ZIP Download Limit](#get-zip-download-limit) - [Get Application Terms of Use](#get-application-terms-of-use) +- [Licenses](#Licenses) + - [Get Available Standard License Terms](#get-available-standard-license-terms) - [Contact](#Contact) - [Send Feedback to Object Contacts](#send-feedback-to-object-contacts) - [Search](#Search) @@ -2084,6 +2086,26 @@ getApplicationTermsOfUse.execute().then((termsOfUse: string) => { _See [use case](../src/info/domain/useCases/GetApplicationTermsOfUse.ts) implementation_. +## Licenses + +### Get Available Standard License Terms + +Returns a list of available standard licenses that can be selected for a dataset. + +##### Example call: + +```typescript +import { getAvailableStandardLicenses, License } from '@iqss/dataverse-client-javascript' + +/* ... */ + +getAvailableStandardLicenses.execute().then((licenses: License[]) => { + /* ... */ +}) +``` + +_See [use case](../src/licenses/domain/useCases/GetAvailableStandardLicenses.ts) implementation_. + ## Contact #### Send Feedback to Object Contacts diff --git a/src/index.ts b/src/index.ts index 2fb70d9e..160e39e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,3 +9,4 @@ export * from './metadataBlocks' export * from './files' export * from './contactInfo' export * from './search' +export * from './licenses' diff --git a/src/licenses/domain/models/License.ts b/src/licenses/domain/models/License.ts new file mode 100644 index 00000000..d1012cb2 --- /dev/null +++ b/src/licenses/domain/models/License.ts @@ -0,0 +1,8 @@ +export interface License { + id: number + name: string + uri: string + iconUrl: string + active: boolean + isDefault: boolean +} diff --git a/src/licenses/domain/repositories/ILicensesRepository.ts b/src/licenses/domain/repositories/ILicensesRepository.ts new file mode 100644 index 00000000..45309a89 --- /dev/null +++ b/src/licenses/domain/repositories/ILicensesRepository.ts @@ -0,0 +1,5 @@ +import { License } from '../models/License' + +export interface ILicensesRepository { + getAvailableStandardLicenses(): Promise +} diff --git a/src/licenses/domain/repositories/transformers/LicensePayload.ts b/src/licenses/domain/repositories/transformers/LicensePayload.ts new file mode 100644 index 00000000..02f4bc58 --- /dev/null +++ b/src/licenses/domain/repositories/transformers/LicensePayload.ts @@ -0,0 +1,14 @@ +export interface LicensePayload { + id: number + name: string + shortDescription: string + uri: string + iconUrl: string + active: boolean + isDefault: boolean + sortOrder: number + rightsIdentifier: string + rightsIdentifierScheme: string + schemeUri: string + languageCode: string +} diff --git a/src/licenses/domain/repositories/transformers/licenseTransformers.ts b/src/licenses/domain/repositories/transformers/licenseTransformers.ts new file mode 100644 index 00000000..7484c23c --- /dev/null +++ b/src/licenses/domain/repositories/transformers/licenseTransformers.ts @@ -0,0 +1,15 @@ +import { AxiosResponse } from 'axios' +import { License } from '../../models/License' +import { LicensePayload } from './LicensePayload' + +export const transformLicensesResponseToLicenses = (response: AxiosResponse): License[] => { + const payload = response.data.data as LicensePayload[] + return payload.map((license: LicensePayload) => ({ + id: license.id, + name: license.name, + uri: license.uri, + iconUrl: license.iconUrl, + active: license.active, + isDefault: license.isDefault + })) +} diff --git a/src/licenses/domain/useCases/GetAvailableStandardLicenses.ts b/src/licenses/domain/useCases/GetAvailableStandardLicenses.ts new file mode 100644 index 00000000..00517770 --- /dev/null +++ b/src/licenses/domain/useCases/GetAvailableStandardLicenses.ts @@ -0,0 +1,20 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { License } from '../models/License' +import { ILicensesRepository } from '../repositories/ILicensesRepository' + +export class GetAvailableStandardLicenses implements UseCase { + private licensesRepository: ILicensesRepository + + constructor(licensesRepository: ILicensesRepository) { + this.licensesRepository = licensesRepository + } + + /** + * Returns the list of available standard license terms that can be selected for a dataset. + * + * @returns {Promise} + */ + async execute(): Promise { + return await this.licensesRepository.getAvailableStandardLicenses() + } +} diff --git a/src/licenses/index.ts b/src/licenses/index.ts new file mode 100644 index 00000000..0d9158f3 --- /dev/null +++ b/src/licenses/index.ts @@ -0,0 +1,10 @@ +import { LicensesRepository } from './infra/repositories/LicensesRepository' +import { GetAvailableStandardLicenses } from './domain/useCases/GetAvailableStandardLicenses' + +const licensesRepository = new LicensesRepository() + +const getAvailableStandardLicenses = new GetAvailableStandardLicenses(licensesRepository) + +export { getAvailableStandardLicenses } + +export { License } from './domain/models/License' diff --git a/src/licenses/infra/repositories/LicensesRepository.ts b/src/licenses/infra/repositories/LicensesRepository.ts new file mode 100644 index 00000000..042fce56 --- /dev/null +++ b/src/licenses/infra/repositories/LicensesRepository.ts @@ -0,0 +1,15 @@ +import { ApiRepository } from '../../../core/infra/repositories/ApiRepository' +import { ILicensesRepository } from '../../domain/repositories/ILicensesRepository' +import { License } from '../../domain/models/License' + +export class LicensesRepository extends ApiRepository implements ILicensesRepository { + private readonly licensesResourceName: string = 'licenses' + + public async getAvailableStandardLicenses(): Promise { + return this.doGet(this.buildApiEndpoint(this.licensesResourceName)) + .then((response) => response.data.data) + .catch((error) => { + throw error + }) + } +} diff --git a/test/functional/licenses/GetAvailableStandardLicenses.test.ts b/test/functional/licenses/GetAvailableStandardLicenses.test.ts new file mode 100644 index 00000000..6e54d24c --- /dev/null +++ b/test/functional/licenses/GetAvailableStandardLicenses.test.ts @@ -0,0 +1,51 @@ +import { ApiConfig, getAvailableStandardLicenses, License } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' + +describe('getAvailableStandardLicenses', () => { + describe('execute', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should return available standard license terms', async () => { + const actualLicenses: License[] = await getAvailableStandardLicenses.execute() + const expectedLicenses = [ + { + id: 1, + name: 'CC0 1.0', + shortDescription: 'Creative Commons CC0 1.0 Universal Public Domain Dedication.', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUrl: 'https://licensebuttons.net/p/zero/1.0/88x31.png', + active: true, + isDefault: true, + sortOrder: 0, + rightsIdentifier: 'CC0-1.0', + rightsIdentifierScheme: 'SPDX', + schemeUri: 'https://spdx.org/licenses/', + languageCode: 'en' + }, + { + id: 2, + name: 'CC BY 4.0', + shortDescription: 'Creative Commons Attribution 4.0 International License.', + uri: 'http://creativecommons.org/licenses/by/4.0', + iconUrl: 'https://licensebuttons.net/l/by/4.0/88x31.png', + active: true, + isDefault: false, + sortOrder: 2, + rightsIdentifier: 'CC-BY-4.0', + rightsIdentifierScheme: 'SPDX', + schemeUri: 'https://spdx.org/licenses/', + languageCode: 'en' + } + ] + + expect(actualLicenses).toEqual(expectedLicenses) + }) + }) +}) diff --git a/test/integration/licenses/LicensesRepository.test.ts b/test/integration/licenses/LicensesRepository.test.ts new file mode 100644 index 00000000..26f27228 --- /dev/null +++ b/test/integration/licenses/LicensesRepository.test.ts @@ -0,0 +1,57 @@ +import { + ApiConfig, + DataverseApiAuthMechanism +} from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' +import { LicensesRepository } from '../../../src/licenses/infra/repositories/LicensesRepository' + +describe('LicensesRepository', () => { + const sut: LicensesRepository = new LicensesRepository() + + describe('getAvailableStandardLicenses', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should return list of available standard license terms', async () => { + const actual = await sut.getAvailableStandardLicenses() + + const licenses = [ + { + id: 1, + name: 'CC0 1.0', + shortDescription: 'Creative Commons CC0 1.0 Universal Public Domain Dedication.', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUrl: 'https://licensebuttons.net/p/zero/1.0/88x31.png', + active: true, + isDefault: true, + sortOrder: 0, + rightsIdentifier: 'CC0-1.0', + rightsIdentifierScheme: 'SPDX', + schemeUri: 'https://spdx.org/licenses/', + languageCode: 'en' + }, + { + id: 2, + name: 'CC BY 4.0', + shortDescription: 'Creative Commons Attribution 4.0 International License.', + uri: 'http://creativecommons.org/licenses/by/4.0', + iconUrl: 'https://licensebuttons.net/l/by/4.0/88x31.png', + active: true, + isDefault: false, + sortOrder: 2, + rightsIdentifier: 'CC-BY-4.0', + rightsIdentifierScheme: 'SPDX', + schemeUri: 'https://spdx.org/licenses/', + languageCode: 'en' + } + ] + + expect(actual).toEqual(licenses) + }) + }) +}) diff --git a/test/unit/licenses/GetAvailableStandardLicenses.test.ts b/test/unit/licenses/GetAvailableStandardLicenses.test.ts new file mode 100644 index 00000000..ea0f7c39 --- /dev/null +++ b/test/unit/licenses/GetAvailableStandardLicenses.test.ts @@ -0,0 +1,52 @@ +import { ReadError } from '../../../src' +import { ILicensesRepository } from '../../../src/licenses/domain/repositories/ILicensesRepository' +import { GetAvailableStandardLicenses } from '../../../src/licenses/domain/useCases/GetAvailableStandardLicenses' + +describe('GetAvailableStandardLicenses', () => { + describe('execute', () => { + test('should return licenses array on repository success', async () => { + const licensesRepositoryStub: ILicensesRepository = {} as ILicensesRepository + + const testLicenses = [ + { + id: 1, + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUrl: 'https://licensebuttons.net/p/zero/1.0/88x31.png', + active: true, + isDefault: true + }, + { + id: 2, + name: 'CC BY 4.0', + uri: 'http://creativecommons.org/licenses/by/4.0', + iconUrl: 'https://licensebuttons.net/l/by/4.0/88x31.png', + active: true, + isDefault: false + } + ] + + licensesRepositoryStub.getAvailableStandardLicenses = jest + .fn() + .mockResolvedValue(testLicenses) + const sut = new GetAvailableStandardLicenses(licensesRepositoryStub) + + const actual = await sut.execute() + + expect(actual).toEqual(testLicenses) + expect(licensesRepositoryStub.getAvailableStandardLicenses).toHaveBeenCalledTimes(1) + }) + + test('should return error result on repository error', async () => { + const licensesRepositoryStub: ILicensesRepository = {} as ILicensesRepository + const expectedError = new ReadError('Failed to fetch licenses') + licensesRepositoryStub.getAvailableStandardLicenses = jest + .fn() + .mockRejectedValue(expectedError) + const sut = new GetAvailableStandardLicenses(licensesRepositoryStub) + + await expect(sut.execute()).rejects.toThrow(ReadError) + expect(licensesRepositoryStub.getAvailableStandardLicenses).toHaveBeenCalledTimes(1) + }) + }) +}) From 47d25619da38e84d3b0161f40c0fa2becbcf0326 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 8 Sep 2025 13:26:40 -0400 Subject: [PATCH 2/2] fix: model and payload changes --- src/licenses/domain/models/License.ts | 8 ++++++- .../transformers/LicensePayload.ts | 12 +++++----- .../transformers/licenseTransformers.ts | 13 ++++++++--- .../GetAvailableStandardLicenses.test.ts | 22 ++++++++++++++----- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/licenses/domain/models/License.ts b/src/licenses/domain/models/License.ts index d1012cb2..7f16442e 100644 --- a/src/licenses/domain/models/License.ts +++ b/src/licenses/domain/models/License.ts @@ -1,8 +1,14 @@ export interface License { id: number name: string + shortDescription?: string uri: string - iconUrl: string + iconUri?: string active: boolean isDefault: boolean + sortOrder: number + rightsIdentifier?: string + rightsIdentifierScheme?: string + schemeUri?: string + languageCode?: string } diff --git a/src/licenses/domain/repositories/transformers/LicensePayload.ts b/src/licenses/domain/repositories/transformers/LicensePayload.ts index 02f4bc58..a67228da 100644 --- a/src/licenses/domain/repositories/transformers/LicensePayload.ts +++ b/src/licenses/domain/repositories/transformers/LicensePayload.ts @@ -1,14 +1,14 @@ export interface LicensePayload { id: number name: string - shortDescription: string + shortDescription?: string uri: string - iconUrl: string + iconUrl?: string active: boolean isDefault: boolean sortOrder: number - rightsIdentifier: string - rightsIdentifierScheme: string - schemeUri: string - languageCode: string + rightsIdentifier?: string + rightsIdentifierScheme?: string + schemeUri?: string + languageCode?: string } diff --git a/src/licenses/domain/repositories/transformers/licenseTransformers.ts b/src/licenses/domain/repositories/transformers/licenseTransformers.ts index 7484c23c..38883f3c 100644 --- a/src/licenses/domain/repositories/transformers/licenseTransformers.ts +++ b/src/licenses/domain/repositories/transformers/licenseTransformers.ts @@ -2,14 +2,21 @@ import { AxiosResponse } from 'axios' import { License } from '../../models/License' import { LicensePayload } from './LicensePayload' -export const transformLicensesResponseToLicenses = (response: AxiosResponse): License[] => { +export const transformPayloadToLicense = (response: AxiosResponse): License[] => { const payload = response.data.data as LicensePayload[] + return payload.map((license: LicensePayload) => ({ id: license.id, name: license.name, + shortDescription: license.shortDescription, uri: license.uri, - iconUrl: license.iconUrl, + iconUri: license.iconUrl, // in payload, it is called iconUrl, but iconUri is the name matching everywhere else active: license.active, - isDefault: license.isDefault + isDefault: license.isDefault, + sortOrder: license.sortOrder, + rightsIdentifier: license.rightsIdentifier, + rightsIdentifierScheme: license.rightsIdentifierScheme, + schemeUri: license.schemeUri, + languageCode: license.languageCode })) } diff --git a/test/unit/licenses/GetAvailableStandardLicenses.test.ts b/test/unit/licenses/GetAvailableStandardLicenses.test.ts index ea0f7c39..4c6857b4 100644 --- a/test/unit/licenses/GetAvailableStandardLicenses.test.ts +++ b/test/unit/licenses/GetAvailableStandardLicenses.test.ts @@ -1,4 +1,4 @@ -import { ReadError } from '../../../src' +import { License, ReadError } from '../../../src' import { ILicensesRepository } from '../../../src/licenses/domain/repositories/ILicensesRepository' import { GetAvailableStandardLicenses } from '../../../src/licenses/domain/useCases/GetAvailableStandardLicenses' @@ -7,22 +7,32 @@ describe('GetAvailableStandardLicenses', () => { test('should return licenses array on repository success', async () => { const licensesRepositoryStub: ILicensesRepository = {} as ILicensesRepository - const testLicenses = [ + const testLicenses: License[] = [ { id: 1, name: 'CC0 1.0', uri: 'http://creativecommons.org/publicdomain/zero/1.0', - iconUrl: 'https://licensebuttons.net/p/zero/1.0/88x31.png', + iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png', active: true, - isDefault: true + isDefault: true, + sortOrder: 0, + rightsIdentifier: 'CC0-1.0', + rightsIdentifierScheme: 'SPDX', + schemeUri: 'https://spdx.org/licenses/', + languageCode: 'en' }, { id: 2, name: 'CC BY 4.0', uri: 'http://creativecommons.org/licenses/by/4.0', - iconUrl: 'https://licensebuttons.net/l/by/4.0/88x31.png', + iconUri: 'https://licensebuttons.net/l/by/4.0/88x31.png', active: true, - isDefault: false + isDefault: false, + sortOrder: 2, + rightsIdentifier: 'CC-BY-4.0', + rightsIdentifierScheme: 'SPDX', + schemeUri: 'https://spdx.org/licenses/', + languageCode: 'en' } ]