tududi/backend/tests/unit/services/applyPerms.test.js
Chris 3486541272
Add comprehensive LLM development documentation (#939)
* Increase coverage

* Add comprehensive LLM development documentation

- Add CLAUDE.md as main documentation index
- Create 8 detailed documentation files in docs/:
  - architecture.md: Tech stack, data models, auth system
  - directory-structure.md: Complete file tree with paths
  - backend-patterns.md: Module architecture and patterns
  - database.md: Models, migrations, and workflows
  - development-workflow.md: Setup and daily development
  - code-conventions.md: Style guide and best practices
  - testing.md: Test organization and patterns
  - common-tasks.md: How-to guides for frequent tasks
- Update .gitignore to allow project-level CLAUDE.md
- 4,285 lines of comprehensive documentation
- Organized for easy navigation with cross-links
- LLM-optimized with absolute paths and code examples

* fixup! Add comprehensive LLM development documentation
2026-03-14 02:54:59 +02:00

258 lines
8.2 KiB
JavaScript

const { applyPerms } = require('../../../services/applyPerms');
const { sequelize, Permission, User } = require('../../../models');
const bcrypt = require('bcrypt');
describe('applyPerms', () => {
let owner, target;
beforeEach(async () => {
await sequelize.query('DELETE FROM permissions');
const hash = await bcrypt.hash('pass', 10);
owner = await User.create({
email: 'owner@test.com',
password_digest: hash,
});
target = await User.create({
email: 'target@test.com',
password_digest: hash,
});
});
it('should create a new permission on upsert when none exists', async () => {
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [
{
userId: target.id,
resourceType: 'project',
resourceUid: 'proj-uid-1',
accessLevel: 'ro',
propagation: 'direct',
grantedByUserId: owner.id,
},
],
deletes: [],
});
});
const perm = await Permission.findOne({
where: {
user_id: target.id,
resource_type: 'project',
resource_uid: 'proj-uid-1',
},
});
expect(perm).not.toBeNull();
expect(perm.access_level).toBe('ro');
expect(perm.propagation).toBe('direct');
expect(perm.granted_by_user_id).toBe(owner.id);
});
it('should upgrade access level on upsert when permission already exists (ro -> rw)', async () => {
// Create existing ro permission
await Permission.create({
user_id: target.id,
resource_type: 'project',
resource_uid: 'proj-uid-2',
access_level: 'ro',
propagation: 'direct',
granted_by_user_id: owner.id,
});
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [
{
userId: target.id,
resourceType: 'project',
resourceUid: 'proj-uid-2',
accessLevel: 'rw',
grantedByUserId: owner.id,
},
],
deletes: [],
});
});
const perm = await Permission.findOne({
where: {
user_id: target.id,
resource_type: 'project',
resource_uid: 'proj-uid-2',
},
});
expect(perm.access_level).toBe('rw');
});
it('should keep rw when upserting ro on existing rw permission', async () => {
await Permission.create({
user_id: target.id,
resource_type: 'project',
resource_uid: 'proj-uid-3',
access_level: 'rw',
propagation: 'direct',
granted_by_user_id: owner.id,
});
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [
{
userId: target.id,
resourceType: 'project',
resourceUid: 'proj-uid-3',
accessLevel: 'ro',
grantedByUserId: owner.id,
},
],
deletes: [],
});
});
const perm = await Permission.findOne({
where: {
user_id: target.id,
resource_type: 'project',
resource_uid: 'proj-uid-3',
},
});
expect(perm.access_level).toBe('rw');
});
it('should delete a permission', async () => {
await Permission.create({
user_id: target.id,
resource_type: 'task',
resource_uid: 'task-uid-1',
access_level: 'rw',
propagation: 'direct',
granted_by_user_id: owner.id,
});
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [],
deletes: [
{
userId: target.id,
resourceType: 'task',
resourceUid: 'task-uid-1',
},
],
});
});
const perm = await Permission.findOne({
where: {
user_id: target.id,
resource_type: 'task',
resource_uid: 'task-uid-1',
},
});
expect(perm).toBeNull();
});
it('should handle empty upserts and deletes', async () => {
await sequelize.transaction(async (tx) => {
await applyPerms(tx, { upserts: [], deletes: [] });
});
// No error thrown
});
it('should handle multiple upserts and deletes in one call', async () => {
await Permission.create({
user_id: target.id,
resource_type: 'note',
resource_uid: 'note-to-delete',
access_level: 'ro',
propagation: 'direct',
granted_by_user_id: owner.id,
});
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [
{
userId: target.id,
resourceType: 'project',
resourceUid: 'new-proj',
accessLevel: 'rw',
propagation: 'direct',
grantedByUserId: owner.id,
},
{
userId: target.id,
resourceType: 'task',
resourceUid: 'new-task',
accessLevel: 'ro',
propagation: 'inherited',
grantedByUserId: owner.id,
},
],
deletes: [
{
userId: target.id,
resourceType: 'note',
resourceUid: 'note-to-delete',
},
],
});
});
const projPerm = await Permission.findOne({
where: { user_id: target.id, resource_uid: 'new-proj' },
});
const taskPerm = await Permission.findOne({
where: { user_id: target.id, resource_uid: 'new-task' },
});
const notePerm = await Permission.findOne({
where: { user_id: target.id, resource_uid: 'note-to-delete' },
});
expect(projPerm).not.toBeNull();
expect(projPerm.access_level).toBe('rw');
expect(taskPerm).not.toBeNull();
expect(taskPerm.access_level).toBe('ro');
expect(taskPerm.propagation).toBe('inherited');
expect(notePerm).toBeNull();
});
it('should set default propagation to direct when not specified', async () => {
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [
{
userId: target.id,
resourceType: 'project',
resourceUid: 'proj-default',
accessLevel: 'ro',
grantedByUserId: owner.id,
// no propagation
},
],
deletes: [],
});
});
const perm = await Permission.findOne({
where: { user_id: target.id, resource_uid: 'proj-default' },
});
expect(perm.propagation).toBe('direct');
});
it('should not fail when deleting a non-existent permission', async () => {
await sequelize.transaction(async (tx) => {
await applyPerms(tx, {
upserts: [],
deletes: [
{
userId: target.id,
resourceType: 'project',
resourceUid: 'does-not-exist',
},
],
});
});
// Should not throw
});
});