Skip to content

Commit

Permalink
Merge pull request #4874 from specify/issue-4868
Browse files Browse the repository at this point in the history
Prevent adding children to synonym nodes when not authorized
  • Loading branch information
CarolineDenis authored May 21, 2024
2 parents 31805d5 + f453036 commit dfb16cb
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { renderHook } from '@testing-library/react';
import { overrideAjax } from '../../../tests/ajax';
import { mockTime, requireContext } from '../../../tests/helpers';
import { overwriteReadOnly } from '../../../utils/types';
import { getPref } from '../../InitialContext/remotePrefs';
import type { SerializedResource } from '../helperTypes';
import { getResourceApiUrl } from '../resource';
import { useSaveBlockers } from '../saveBlockers';
Expand Down Expand Up @@ -197,6 +198,18 @@ describe('treeBusinessRules', () => {
_tableName: 'Taxon',
id: 3,
name: 'Acipenser',
isAccepted: true,
rankId: 180,
definition: '/api/specify/taxontreedef/1/',
definitionItem: '/api/specify/taxontreedefitem/9/',
parent: '/api/specify/taxon/2/',
};

const husoResponse: Partial<SerializedResource<Taxon>> = {
_tableName: 'Taxon',
id: 6,
name: 'Huso',
isAccepted: false,
rankId: 180,
definition: '/api/specify/taxontreedef/1/',
definitionItem: '/api/specify/taxontreedefitem/9/',
Expand All @@ -207,6 +220,7 @@ describe('treeBusinessRules', () => {
_tableName: 'Taxon',
id: 4,
name: 'oxyrinchus',
isAccepted: true,
rankId: 220,
definition: '/api/specify/taxontreedef/1/',
definitionItem: '/api/specify/taxontreedefitem/2/',
Expand All @@ -218,6 +232,7 @@ describe('treeBusinessRules', () => {
id: 5,
rankId: 230,
name: 'oxyrinchus',
isAccepted: true,
definition: '/api/specify/taxontreedef/1/',
definitionItem: '/api/specify/taxontreedefitem/22/',
};
Expand Down Expand Up @@ -265,9 +280,11 @@ describe('treeBusinessRules', () => {
};

const oxyrinchusFullNameResponse = 'Acipenser oxyrinchus';
const dauricusFullNameResponse = 'Huso dauricus';

overrideAjax('/api/specify/taxon/2/', animaliaResponse);
overrideAjax('/api/specify/taxon/3/', acipenserResponse);
overrideAjax('/api/specify/taxon/6/', husoResponse);
overrideAjax('/api/specify/taxon/4/', oxyrinchusSpeciesResponse);
overrideAjax('/api/specify/taxon/5/', oxyrinchusSubSpeciesResponse);
overrideAjax('/api/specify/taxontreedefitem/9/', genusResponse);
Expand All @@ -277,6 +294,10 @@ describe('treeBusinessRules', () => {
'/api/specify_tree/taxon/3/predict_fullname/?name=oxyrinchus&treedefitemid=2',
oxyrinchusFullNameResponse
);
overrideAjax(
'/api/specify_tree/taxon/6/predict_fullname/?name=dauricus&treedefitemid=2',
dauricusFullNameResponse
);
overrideAjax('/api/specify/taxon/?limit=1&parent=4&orderby=rankid', {
objects: [oxyrinchusSubSpeciesResponse],
meta: {
Expand Down Expand Up @@ -329,4 +350,52 @@ describe('treeBusinessRules', () => {
);
expect(result.current[0]).toStrictEqual(['Bad tree structure.']);
});
test('saveBlocker on synonymized parent', async () => {
const taxon = new tables.Taxon.Resource({
name: 'dauricus',
parent: '/api/specify/taxon/6/',
rankId: 220,
definition: '/api/specify/taxontreedef/1/',
definitionItem: '/api/specify/taxontreedefitem/2/',
});

await taxon.businessRuleManager?.checkField('parent');

const { result } = renderHook(() =>
useSaveBlockers(taxon, tables.Taxon.getField('parent'))
);
expect(result.current[0]).toStrictEqual(['Bad tree structure.']);

await taxon.businessRuleManager?.checkField('integer1');

const { result: fieldChangeResult } = renderHook(() =>
useSaveBlockers(taxon, tables.Taxon.getField('parent'))
);
expect(fieldChangeResult.current[0]).toStrictEqual(['Bad tree structure.']);
});
test('saveBlocker not on synonymized parent w/preference', async () => {
const remotePrefs = await import('../../InitialContext/remotePrefs');
jest
.spyOn(remotePrefs, 'getPref')
.mockImplementation((key) =>
key === 'sp7.allow_adding_child_to_synonymized_parent.Taxon'
? true
: getPref(key)
);

const taxon = new tables.Taxon.Resource({
name: 'dauricus',
parent: '/api/specify/taxon/6/',
rankId: 220,
definition: '/api/specify/taxontreedef/1/',
definitionItem: '/api/specify/taxontreedefitem/2/',
});

await taxon.businessRuleManager?.checkField('parent');

const { result } = renderHook(() =>
useSaveBlockers(taxon, tables.Taxon.getField('parent'))
);
expect(result.current[0]).toStrictEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { treeText } from '../../localization/tree';
import { ajax } from '../../utils/ajax';
import { f } from '../../utils/functools';
import { getPref } from '../InitialContext/remotePrefs';
import { fetchPossibleRanks } from '../PickLists/TreeLevelPickList';
import { formatUrl } from '../Router/queryString';
import type { BusinessRuleResult } from './businessRules';
Expand Down Expand Up @@ -37,9 +38,15 @@ export const treeBusinessRules = async (
? undefined
: await fetchPossibleRanks(resource, parentDefItem.get('rankId'));

const doExpandSynonymActionsPref = getPref(
`sp7.allow_adding_child_to_synonymized_parent.${resource.specifyTable.name}`
);
const isParentSynonym = !parent.get('isAccepted');

const hasBadTreeStrcuture =
parent.id === resource.id ||
definitionItem === undefined ||
(isParentSynonym && !doExpandSynonymActionsPref) ||
parent.get('rankId') >= definitionItem.get('rankId') ||
(possibleRanks !== undefined &&
!possibleRanks
Expand All @@ -60,6 +67,7 @@ export const treeBusinessRules = async (
};

if (
hasBadTreeStrcuture ||
(resource.get('name')?.length ?? 0) === 0 ||
definitionItem === undefined
)
Expand Down

0 comments on commit dfb16cb

Please sign in to comment.