mirror of
https://github.com/Gimanh/taskview-community.git
synced 2026-09-11 13:29:17 +00:00
wip: tests and permission descriptions
This commit is contained in:
@@ -538,5 +538,16 @@
|
||||
"Added compound indexes on tasks.time_entries (goal_id, started_at, user_id) and (goal_id, started_at, task_id) for time-tracking report aggregations",
|
||||
"Added partial index on tasks.time_entries(goal_id, started_at) WHERE billable = TRUE for billable-only reports"
|
||||
]
|
||||
},
|
||||
"43": {
|
||||
"version": "1.50.0",
|
||||
"name": "Release 1.50.0",
|
||||
"releaseDate": "20260514",
|
||||
"scripts": [
|
||||
"/1.50.0/0.update-timetracking-permissions-descriptions.sql"
|
||||
],
|
||||
"description": [
|
||||
"Updated descriptions of timetracking_can_view and timetracking_can_manage_all to document that these permissions also expose contributor emails through the time-entry log"
|
||||
]
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
UPDATE tv_auth.permissions
|
||||
SET
|
||||
description = 'View all time entries on this project — both own and other members''. Also exposes the emails of all contributors via the time-entry log',
|
||||
description_locales = '{"en": "View time entries. User can see all time entries on this project — both own and other members''. Also exposes the emails of all contributors who have logged time on this project. Does not grant logging or editing.", "ru": "Просмотр записей времени. Пользователь видит все записи проекта — свои и других участников. Также видны email-адреса всех участников, логировавших время. Логировать и редактировать нельзя."}'::jsonb
|
||||
WHERE name = 'timetracking_can_view';
|
||||
|
||||
UPDATE tv_auth.permissions
|
||||
SET
|
||||
description = 'Full time-tracking access: log own time + view and edit/delete entries of any project member. Also exposes emails of all contributors',
|
||||
description_locales = '{"en": "Manage all time entries. Full time-tracking access on this project: user can log own time, view all entries (own and other members''), and edit/delete entries of any project member. Also exposes the emails of all contributors who have logged time on this project. Implies both view and log permissions.", "ru": "Управление всеми записями времени. Полный доступ к учёту времени на этом проекте: пользователь может вести свой таймер, видеть все записи (свои и других участников) и редактировать/удалять записи любого участника. Также видны email-адреса всех участников, логировавших время. Включает права на просмотр и ведение времени."}'::jsonb
|
||||
WHERE name = 'timetracking_can_manage_all';
|
||||
@@ -107,6 +107,8 @@ const Timezone = type('string').narrow((v, ctx) => {
|
||||
}
|
||||
})
|
||||
|
||||
const MAX_REPORT_WINDOW_MS = 2 * 365 * 24 * 60 * 60 * 1000
|
||||
|
||||
export const TimeReportArkTypeFilters = type({
|
||||
'goalIds?': NumberListFromString,
|
||||
'userId?': OptionalNumberFromString,
|
||||
@@ -114,6 +116,19 @@ export const TimeReportArkTypeFilters = type({
|
||||
to: DateFromString,
|
||||
'billable?': BooleanFromString,
|
||||
'timezone?': Timezone,
|
||||
}).narrow((data, ctx) => {
|
||||
const fromMs = data.from.getTime()
|
||||
const toMs = data.to.getTime()
|
||||
if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) {
|
||||
return ctx.mustBe('valid from/to dates')
|
||||
}
|
||||
if (fromMs >= toMs) {
|
||||
return ctx.mustBe('from earlier than to')
|
||||
}
|
||||
if (toMs - fromMs > MAX_REPORT_WINDOW_MS) {
|
||||
return ctx.mustBe('range no wider than 2 years')
|
||||
}
|
||||
return true
|
||||
})
|
||||
export type TimeReportFilters = typeof TimeReportArkTypeFilters.infer
|
||||
|
||||
|
||||
@@ -963,6 +963,65 @@ describe('TvApi time-tracking tests', () => {
|
||||
expect(status).toBe(400);
|
||||
});
|
||||
|
||||
it('reportSummary with from >= to is rejected (400)', async () => {
|
||||
const status = await $mainUser.timeTracking.reportSummary({
|
||||
organizationId,
|
||||
from: isoMinutesFromNow(60),
|
||||
to: isoMinutesAgo(60),
|
||||
}).catch((err) => err.status);
|
||||
expect(status).toBe(400);
|
||||
});
|
||||
|
||||
it('reportSummary with from == to is rejected (400)', async () => {
|
||||
const sameMoment = new Date().toISOString();
|
||||
const status = await $mainUser.timeTracking.reportSummary({
|
||||
organizationId,
|
||||
from: sameMoment,
|
||||
to: sameMoment,
|
||||
}).catch((err) => err.status);
|
||||
expect(status).toBe(400);
|
||||
});
|
||||
|
||||
it('reportSummary with date range wider than 2 years is rejected (400)', async () => {
|
||||
const status = await $mainUser.timeTracking.reportSummary({
|
||||
organizationId,
|
||||
from: '2000-01-01T00:00:00Z',
|
||||
to: '2099-12-31T23:59:59Z',
|
||||
}).catch((err) => err.status);
|
||||
expect(status).toBe(400);
|
||||
});
|
||||
|
||||
it('reportByDay with date range wider than 2 years is rejected (400)', async () => {
|
||||
const status = await $mainUser.timeTracking.reportByDay({
|
||||
organizationId,
|
||||
from: '2000-01-01T00:00:00Z',
|
||||
to: '2099-12-31T23:59:59Z',
|
||||
}).catch((err) => err.status);
|
||||
expect(status).toBe(400);
|
||||
});
|
||||
|
||||
it('reportContributors with from >= to is rejected (400)', async () => {
|
||||
const status = await $mainUser.timeTracking.reportContributors({
|
||||
organizationId,
|
||||
from: isoMinutesFromNow(60),
|
||||
to: isoMinutesAgo(60),
|
||||
}).catch((err) => err.status);
|
||||
expect(status).toBe(400);
|
||||
});
|
||||
|
||||
it('report with exactly 2-year window is accepted', async () => {
|
||||
const twoYearsAgo = new Date()
|
||||
twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2)
|
||||
twoYearsAgo.setHours(twoYearsAgo.getHours() + 1)
|
||||
const result = await $mainUser.timeTracking.reportSummary({
|
||||
organizationId,
|
||||
from: twoYearsAgo.toISOString(),
|
||||
to: new Date().toISOString(),
|
||||
}).catch((err) => err.status);
|
||||
expect(typeof result).toBe('object');
|
||||
expect((result as { totalSeconds: number }).totalSeconds).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it('deleting an active timer removes it and clears active state', async () => {
|
||||
const started = await $mainUser.timeTracking.start({ taskId }).catch(console.error);
|
||||
expect(started?.entry).toBeDefined();
|
||||
|
||||
+20
@@ -245,6 +245,26 @@ describe('time-tracking integration', () => {
|
||||
expect(result.isError).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects report with from >= to', async () => {
|
||||
const result = await call(tools, 'get_time_report', {
|
||||
scope: 'summary',
|
||||
organizationId,
|
||||
from: iso(60),
|
||||
to: iso(-60),
|
||||
})
|
||||
expect(result.isError).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects report with date range wider than 2 years', async () => {
|
||||
const result = await call(tools, 'get_time_report', {
|
||||
scope: 'summary',
|
||||
organizationId,
|
||||
from: '2000-01-01T00:00:00Z',
|
||||
to: '2099-12-31T23:59:59Z',
|
||||
})
|
||||
expect(result.isError).toBe(true)
|
||||
})
|
||||
|
||||
it('deletes a time entry', async () => {
|
||||
const created = await call(tools, 'log_time', {
|
||||
taskId,
|
||||
|
||||
Reference in New Issue
Block a user