Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
## v1.0

### Updated
- Updated License resolver to remove pagination from `licenses` query. It now returns all licenses.
- Updated tests and isValid functions on `Question`, `VersionedQuestion` and `Answer` to work with new version of `@dmptool/types` v2.0
- Related works stored procedures so that they can insert existing related works and ground truth data.

### Added
- Added unit tests for the license resolver
- Added endpoint for returning summary stats for related works associated with a plan.
- Added ability to manually add a related work via a DOI.
- Added `findByURIs` methods to both `Repository` and `MetadataStandards` models [#572]
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@aws-sdk/client-ssm": "3.972.0",
"@aws-sdk/credential-providers": "3.972.0",
"@aws-sdk/util-dynamodb": "3.972.0",
"@dmptool/types": "2.3.2",
"@dmptool/types": "3.1.0",
"@elastic/ecs-pino-format": "1.5.0",
"@graphql-tools/merge": "9.1.7",
"@graphql-tools/mock": "9.1.5",
Expand Down
13 changes: 13 additions & 0 deletions src/__mocks__/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ export const mockToken = async (
}
}

export const mockResearcherToken = async (): Promise<JWTAccessToken> => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these to facilitate future resolver unit tests

const token = await mockToken();
return { ...token, role: UserRole.RESEARCHER };
}
export const mockAdminToken = async (): Promise<JWTAccessToken> => {
const token = await mockToken();
return { ...token, role: UserRole.ADMIN };
}
export const mockSuperAdminToken = async (): Promise<JWTAccessToken> => {
const token = await mockToken();
return { ...token, role: UserRole.SUPERADMIN };
}

export const mockDataSources = {
dmphubAPIDataSource: new DMPHubAPI({ cache: null, token: null}),
sqlDataSource: mockedMysqlInstance,
Expand Down
81 changes: 19 additions & 62 deletions src/models/License.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { MyContext } from "../context";
import { prepareObjectForLogs } from "../logger";
import { PaginatedQueryResults, PaginationOptions, PaginationOptionsForCursors, PaginationOptionsForOffsets, PaginationType } from "../types/general";
import { isNullOrUndefined, randomHex, validateURL } from "../utils/helpers";
import { randomHex, validateURL } from "../utils/helpers";
import { MySqlModel } from "./MySqlModel";

export const DEFAULT_DMPTOOL_LICENSE_URL = 'https://dmptool.org/licenses/';;
export const DEFAULT_DMPTOOL_LICENSE_URL = 'https://dmptool.org/licenses/';

export class License extends MySqlModel {
public name: string;
public uri: string;
public description?: string;
public recommended: boolean;

private tableName = 'licenses';
private static tableName = 'licenses';

constructor(options) {
super(options.id, options.created, options.createdById, options.modified, options.modifiedById, options.errors);
Expand Down Expand Up @@ -63,7 +61,7 @@ export class License extends MySqlModel {
this.addError('general', 'License already exists');
} else {
// Save the record and then fetch it
const newId = await License.insert(context, this.tableName, this, reference);
const newId = await License.insert(context, License.tableName, this, reference);
const response = await License.findById(reference, context, newId);
return response;
}
Expand All @@ -79,7 +77,7 @@ export class License extends MySqlModel {
this.prepForSave();
if (await this.isValid()) {
if (id) {
await License.update(context, this.tableName, this, 'License.update', [], noTouch);
await License.update(context, License.tableName, this, 'License.update', [], noTouch);
return await License.findById('License.update', context, id);
}
// This template has never been saved before so we cannot update it!
Expand All @@ -95,7 +93,7 @@ export class License extends MySqlModel {

const successfullyDeleted = await License.delete(
context,
this.tableName,
License.tableName,
this.id,
'License.delete'
);
Expand All @@ -110,77 +108,36 @@ export class License extends MySqlModel {

// Fetch a License by it's id
static async findById(reference: string, context: MyContext, licenseId: number): Promise<License> {
const sql = `SELECT * FROM licenses WHERE id = ?`;
const sql = `SELECT * FROM ${License.tableName} WHERE id = ?`;
const results = await License.query(context, sql, [licenseId?.toString()], reference);
return Array.isArray(results) && results.length > 0 ? new License(results[0]) : null;
}

// Find a License by its URI. The URI is case insensitive.
static async findByURI(reference: string, context: MyContext, uri: string): Promise<License> {
const sql = `SELECT * FROM licenses WHERE uri = ?`;
const sql = `SELECT * FROM ${License.tableName} WHERE uri = ?`;
const results = await License.query(context, sql, [uri], reference);
return Array.isArray(results) && results.length > 0 ? new License(results[0]) : null;
}

// Find a License by its name. The name is case insensitive.
static async findByName(reference: string, context: MyContext, name: string): Promise<License> {
const sql = `SELECT * FROM licenses WHERE LOWER(name) = ?`;
const sql = `SELECT * FROM ${License.tableName} WHERE LOWER(name) = ?`;
const results = await License.query(context, sql, [name?.toLowerCase()?.trim()], reference);
return Array.isArray(results) && results.length > 0 ? new License(results[0]) : null;
}

// Find licenses that match the search term
static async search(
reference: string,
context: MyContext,
name: string,
options: PaginationOptions = License.getDefaultPaginationOptions(),
): Promise<PaginatedQueryResults<License>> {
const whereFilters = [];
const values = [];

// Handle the incoming search term
const searchTerm = (name ?? '').toLowerCase().trim();
if (!isNullOrUndefined(searchTerm)) {
whereFilters.push('(LOWER(l.name) LIKE ? OR LOWER(l.description) LIKE ?)');
values.push(`%${searchTerm}%`, `%${searchTerm}%`);
}

// Set the default sort field and order if none was provided
if (isNullOrUndefined(options.sortField)) options.sortField = 'l.name';
if (isNullOrUndefined(options.sortDir)) options.sortDir = 'ASC';

// Specify the fields available for sorting
options.availableSortFields = ['l.name', 'l.created', 'l.recommended'];
// Specify the field we want to use for the count
options.countField = 'l.id';

// Determine the type of pagination we are using and then set any additional options we need
let opts;
if (options.type === PaginationType.OFFSET) {
opts = options as PaginationOptionsForOffsets;
} else {
opts = options as PaginationOptionsForCursors;
opts.cursorField = 'l.id';
}

const sqlStatement = 'SELECT l.* FROM licenses l';

const response: PaginatedQueryResults<License> = await License.queryWithPagination(
context,
sqlStatement,
whereFilters,
'',
values,
opts,
reference,
)

context.logger.debug(prepareObjectForLogs({ options, response }), reference);
return response;
// Return all licenses
static async all(reference: string, context: MyContext): Promise<License[]> {
const sql = `SELECT * FROM ${License.tableName} ORDER BY name ASC`;
const results = await License.query(context, sql, [], reference);
// No need to initialize new License objects here as they are just search results
return Array.isArray(results) ? results : [];
}

// Find licenses that match the search term
// Find licenses that are either recommended or not recommended
static async recommended(reference: string, context: MyContext, recommended = true): Promise<License[]> {
const sql = `SELECT * FROM licenses WHERE recommended = ?`;
const sql = `SELECT * FROM ${License.tableName} WHERE recommended = ?`;
const vals = recommended ? ['1'] : ['0'];
const results = await License.query(context, sql, vals, reference);
// No need to initialize new License objects here as they are just search results
Expand Down
33 changes: 9 additions & 24 deletions src/models/__tests__/License.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import casual from "casual";
import { buildMockContextWithToken } from "../../__mocks__/context";
import { License } from "../License";
import { generalConfig } from "../../config/generalConfig";
import { logger } from "../../logger";

jest.mock('../../context.ts');
Expand Down Expand Up @@ -145,32 +144,18 @@ describe('findBy Queries', () => {
expect(result).toEqual(null);
});

it('search should call query with correct params and return the objects', async () => {
localPaginationQuery.mockResolvedValueOnce([license]);
const term = casual.company_name;
const result = await License.search('testing', context, term);
const sql = 'SELECT l.* FROM licenses l';
const whereFilters = ['(LOWER(l.name) LIKE ? OR LOWER(l.description) LIKE ?)'];
const vals = [`%${term.toLowerCase().trim()}%`, `%${term.toLowerCase().trim()}%`];
const sortFields = ["l.name", "l.created", "l.recommended"];
const opts = {
cursor: null,
limit: generalConfig.defaultSearchLimit,
sortField: 'l.name',
sortDir: 'ASC',
countField: 'l.id',
cursorField: 'l.id',
availableSortFields: sortFields,
};
expect(localPaginationQuery).toHaveBeenCalledTimes(1);
expect(localPaginationQuery).toHaveBeenLastCalledWith(context, sql, whereFilters, '', vals, opts, 'testing')
it('all should call query with correct params and return the objects', async () => {
localQuery.mockResolvedValueOnce([license]);
const result = await License.all('testing', context);
const expectedSql = 'SELECT * FROM licenses ORDER BY name ASC';
expect(localQuery).toHaveBeenCalledTimes(1);
expect(localQuery).toHaveBeenLastCalledWith(context, expectedSql, [], 'testing')
expect(result).toEqual([license]);
});

it('search should return an empty array if it finds no records', async () => {
localPaginationQuery.mockResolvedValueOnce([]);
const term = casual.company_name;
const result = await License.search('testing', context, term);
it('all should return an empty array if it finds no records', async () => {
localQuery.mockResolvedValueOnce([]);
const result = await License.all('testing', context);
expect(result).toEqual([]);
});

Expand Down
Loading