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
55 changes: 55 additions & 0 deletions specifyweb/backend/context/tests/test_app_resource.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from django.test import Client

from specifyweb.specify.tests.test_api import ApiTests
from specifyweb.specify.models import (
Agent,
Spappresourcedata,
Spappresourcedir,
Spappresource,
Specifyuser,
)
from specifyweb.backend.permissions.models import UserPolicy
from unittest.mock import Mock, patch

from specifyweb.backend.context.app_resource import get_data_view_queries_resource
Expand Down Expand Up @@ -90,3 +98,50 @@ def test_not_found_quiet(self, get_app_resource: Mock):

response = c.get("/context/app.resource?name='simple'&quiet='true'")
self._assertStatusCodeEqual(response, 204)

def test_collection_preferences_cannot_be_modified_without_permission(self):
directory = Spappresourcedir.objects.create(
collection=self.collection,
ispersonal=False,
)
resource = Spappresource.objects.create(
spappresourcedir=directory,
specifyuser=self.specifyuser,
level=0,
name="CollectionPreferences",
mimetype="application/json",
)
Spappresourcedata.objects.create(
spappresource=resource,
data=b'{"general":{}}',
)

user = Specifyuser.objects.create(
isloggedin=False,
isloggedinreport=False,
name="no-permission-user",
password="test",
)
UserPolicy.objects.create(
collection=self.collection,
specifyuser=user,
resource="/system/sp7/collection",
action="access",
)
Agent.objects.create(
agenttype=0,
firstname="No",
lastname="Permission",
division=self.division,
specifyuser=user,
)

c = Client()
c.force_login(user)
response = c.put(
f"/api/specify/spappresource/{resource.id}/",
data={"version": resource.version, "name": "Changed"},
content_type="application/json",
)

self._assertStatusCodeEqual(response, 403)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { deserializeResource } from '../../DataModel/serializers';
import type { Attachment } from '../../DataModel/types';
import { overrideAttachmentSettings } from '../attachments';
import * as Attachments from '../attachments';
import { collectionPreferences } from '../../Preferences/collectionPreferences';
import { UploadAttachment } from '../Plugin';
import { testAttachment } from './utils';

Expand All @@ -26,6 +27,17 @@ beforeEach(() => {
clearIdStore();
});

let originalPreferences: ReturnType<typeof collectionPreferences.getRaw>;

beforeEach(() => {
originalPreferences = collectionPreferences.getRaw();
});

afterEach(() => {
jest.restoreAllMocks();
collectionPreferences.setRaw(originalPreferences);
});

describe('UploadAttachment', () => {
const testToken = 'testToken';
const testAttachmentLocation = 'testLocation';
Expand Down Expand Up @@ -61,4 +73,39 @@ describe('UploadAttachment', () => {
expect(handleUploaded).toHaveBeenCalled();
});
});

test.each([false, true])(
'uses the collection public attachment preference (%s)',
async (isPublic) => {
collectionPreferences.setRaw({
general: {
attachments: { 'attachment.is_public_default': isPublic },
},
});
const uploadFile = jest
.spyOn(Attachments, 'uploadFile')
.mockImplementation(uploadFileMock);
const handleUploaded = jest.fn();

overrideAttachmentSettings(attachmentSettings);
const { container, user } = mount(
<LoadingContext.Provider value={f.void}>
<UploadAttachment onUploaded={handleUploaded} />
</LoadingContext.Provider>
);

const input = Array.from(container.getElementsByTagName('input'))[0];
const testFile = new File(['Some Text Contents'], 'testName', {
type: 'text/plain',
});

await user.upload(input, testFile);

await waitFor(() => {
expect(uploadFile).toHaveBeenCalledWith(
expect.objectContaining({ attachmentIsPublicDefault: isPublic })
);
});
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ describe('uploadFile', () => {
expect(attachment?.get('title')).toBe(testFileName);
});

test.each([false, true])(
'preserves the public attachment default (%s)',
async (isPublic) => {
const attachment = await uploadFile({
file: testFile,
attachmentIsPublicDefault: isPublic,
});

expect(attachment?.get('isPublic')).toBe(isPublic);
}
);

test('sends request correctly (override spec)', async () => {
const newToken = 'newTestToken';
const newLocation = 'newTestLocation';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import type { RA } from '../../../utils/types';
import { localized } from '../../../utils/types';
import { formatNumber } from '../../Atoms/Internationalization';
import {
applyRefreshLayout,
applyStatBackendResponse,
generateStatUrl,
getBackendUrlToFetch,
getDefaultLayoutFlagged,
getOffsetOne,
} from '../hooks';
import { statsSpec } from '../StatsSpec';
import type { StatLayout } from '../types';
import { defaultLayoutTest, statsSpecTest } from './layout.tests';

Expand Down Expand Up @@ -297,3 +299,85 @@ theories(generateStatUrl, [
out: '/stats/collection/user/preparations/',
},
]);

test('preparation totals follow the collection preference', () => {
const spec =
statsSpec.collection.categories.preparations.items.phantomItem.spec;

if (spec.type !== 'BackEndStat') throw new Error('Expected BackEndStat');

expect(
spec.formatterGenerator({ showPreparationsTotal: true })({
lots: 826,
total: 430,
})
).toBe('826 / 430');
expect(
spec.formatterGenerator({ showPreparationsTotal: false })({
lots: 826,
total: 430,
})
).toBe('826');
});

test('stale statistics are cleared after the refresh interval', () => {
const layout: StatLayout = {
label: 'page',
categories: [
{
label: 'category',
items: [
{
type: 'DefaultStat',
itemType: 'BackEndStat',
pageName: 'collection',
categoryName: 'preparations',
itemName: 'phantomItem',
label: localized('preparations'),
itemValue: '826 / 430',
},
],
},
],
lastUpdated: new Date('2024-01-01T00:00:00Z').toJSON(),
};
jest
.spyOn(Date, 'now')
.mockReturnValue(new Date('2024-01-01T01:00:00Z').valueOf());

const [refreshed] = applyRefreshLayout([layout], 30) ?? [];

expect(refreshed.lastUpdated).toBeUndefined();
expect(refreshed.categories[0].items?.[0].itemValue).toBeUndefined();
});

test('fresh statistics are kept before the refresh interval', () => {
const layout: StatLayout = {
label: 'page',
categories: [
{
label: 'category',
items: [
{
type: 'DefaultStat',
itemType: 'BackEndStat',
pageName: 'collection',
categoryName: 'preparations',
itemName: 'phantomItem',
label: localized('preparations'),
itemValue: '826 / 430',
},
],
},
],
lastUpdated: new Date('2024-01-01T00:00:00Z').toJSON(),
};
jest
.spyOn(Date, 'now')
.mockReturnValue(new Date('2024-01-01T00:10:00Z').valueOf());

const [refreshed] = applyRefreshLayout([layout], 30) ?? [];

expect(refreshed.lastUpdated).toBe(layout.lastUpdated);
expect(refreshed.categories[0].items?.[0].itemValue).toBe('826 / 430');
});
Loading