mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 11:22:15 +00:00
fix(core): Use cached value in retrieval of personal project owner (#11533)
Co-authored-by: Danny Martini <danny@n8n.io>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { Project } from '@/databases/entities/project';
|
import { Project } from '@/databases/entities/project';
|
||||||
import { ProjectRelation } from '@/databases/entities/project-relation';
|
import { ProjectRelation } from '@/databases/entities/project-relation';
|
||||||
@@ -13,12 +14,15 @@ import { OwnershipService } from '@/services/ownership.service';
|
|||||||
import { mockCredential, mockProject } from '@test/mock-objects';
|
import { mockCredential, mockProject } from '@test/mock-objects';
|
||||||
import { mockInstance } from '@test/mocking';
|
import { mockInstance } from '@test/mocking';
|
||||||
|
|
||||||
|
import { CacheService } from '../cache/cache.service';
|
||||||
|
|
||||||
describe('OwnershipService', () => {
|
describe('OwnershipService', () => {
|
||||||
const userRepository = mockInstance(UserRepository);
|
const userRepository = mockInstance(UserRepository);
|
||||||
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);
|
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);
|
||||||
const projectRelationRepository = mockInstance(ProjectRelationRepository);
|
const projectRelationRepository = mockInstance(ProjectRelationRepository);
|
||||||
|
const cacheService = mockInstance(CacheService);
|
||||||
const ownershipService = new OwnershipService(
|
const ownershipService = new OwnershipService(
|
||||||
mock(),
|
cacheService,
|
||||||
userRepository,
|
userRepository,
|
||||||
mock(),
|
mock(),
|
||||||
projectRelationRepository,
|
projectRelationRepository,
|
||||||
@@ -52,22 +56,22 @@ describe('OwnershipService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getProjectOwnerCached()', () => {
|
describe('getPersonalProjectOwnerCached()', () => {
|
||||||
test('should retrieve a project owner', async () => {
|
test('should retrieve a project owner', async () => {
|
||||||
const mockProject = new Project();
|
// ARRANGE
|
||||||
const mockOwner = new User();
|
const project = new Project();
|
||||||
|
const owner = new User();
|
||||||
const projectRelation = Object.assign(new ProjectRelation(), {
|
const projectRelation = new ProjectRelation();
|
||||||
role: 'project:personalOwner',
|
projectRelation.role = 'project:personalOwner';
|
||||||
project: mockProject,
|
(projectRelation.project = project), (projectRelation.user = owner);
|
||||||
user: mockOwner,
|
|
||||||
});
|
|
||||||
|
|
||||||
projectRelationRepository.getPersonalProjectOwners.mockResolvedValueOnce([projectRelation]);
|
projectRelationRepository.getPersonalProjectOwners.mockResolvedValueOnce([projectRelation]);
|
||||||
|
|
||||||
|
// ACT
|
||||||
const returnedOwner = await ownershipService.getPersonalProjectOwnerCached('some-project-id');
|
const returnedOwner = await ownershipService.getPersonalProjectOwnerCached('some-project-id');
|
||||||
|
|
||||||
expect(returnedOwner).toBe(mockOwner);
|
// ASSERT
|
||||||
|
expect(returnedOwner).toBe(owner);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not throw if no project owner found, should return null instead', async () => {
|
test('should not throw if no project owner found, should return null instead', async () => {
|
||||||
@@ -77,6 +81,29 @@ describe('OwnershipService', () => {
|
|||||||
|
|
||||||
expect(owner).toBeNull();
|
expect(owner).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not use the repository if the owner was found in the cache', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = new Project();
|
||||||
|
project.id = uuid();
|
||||||
|
const owner = new User();
|
||||||
|
owner.id = uuid();
|
||||||
|
const projectRelation = new ProjectRelation();
|
||||||
|
projectRelation.role = 'project:personalOwner';
|
||||||
|
(projectRelation.project = project), (projectRelation.user = owner);
|
||||||
|
|
||||||
|
cacheService.getHashValue.mockResolvedValueOnce(owner);
|
||||||
|
userRepository.create.mockReturnValueOnce(owner);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const foundOwner = await ownershipService.getPersonalProjectOwnerCached(project.id);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(cacheService.getHashValue).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cacheService.getHashValue).toHaveBeenCalledWith('project-owner', project.id);
|
||||||
|
expect(projectRelationRepository.getPersonalProjectOwners).not.toHaveBeenCalled();
|
||||||
|
expect(foundOwner).toEqual(owner);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getProjectOwnerCached()', () => {
|
describe('getProjectOwnerCached()', () => {
|
||||||
|
|||||||
@@ -45,13 +45,9 @@ export class OwnershipService {
|
|||||||
* Personal project ownership is **immutable**.
|
* Personal project ownership is **immutable**.
|
||||||
*/
|
*/
|
||||||
async getPersonalProjectOwnerCached(projectId: string): Promise<User | null> {
|
async getPersonalProjectOwnerCached(projectId: string): Promise<User | null> {
|
||||||
const cachedValue = await this.cacheService.getHashValue<User | null>(
|
const cachedValue = await this.cacheService.getHashValue<User>('project-owner', projectId);
|
||||||
'project-owner',
|
|
||||||
projectId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cachedValue) this.userRepository.create(cachedValue);
|
if (cachedValue) return this.userRepository.create(cachedValue);
|
||||||
if (cachedValue === null) return null;
|
|
||||||
|
|
||||||
const ownerRel = await this.projectRelationRepository.getPersonalProjectOwners([projectId]);
|
const ownerRel = await this.projectRelationRepository.getPersonalProjectOwners([projectId]);
|
||||||
const owner = ownerRel[0]?.user ?? null;
|
const owner = ownerRel[0]?.user ?? null;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { GlobalConfig } from '@n8n/config';
|
import { GlobalConfig } from '@n8n/config';
|
||||||
import { type Workflow, type INode, type WorkflowSettings } from 'n8n-workflow';
|
import { type Workflow, type INode, type WorkflowSettings } from 'n8n-workflow';
|
||||||
import { strict as assert } from 'node:assert';
|
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
import type { Project } from '@/databases/entities/project';
|
import type { Project } from '@/databases/entities/project';
|
||||||
@@ -68,11 +67,9 @@ export class SubworkflowPolicyChecker {
|
|||||||
|
|
||||||
const owner = await this.ownershipService.getPersonalProjectOwnerCached(subworkflowProject.id);
|
const owner = await this.ownershipService.getPersonalProjectOwnerCached(subworkflowProject.id);
|
||||||
|
|
||||||
assert(owner !== null); // only `null` if not personal
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasReadAccess,
|
hasReadAccess,
|
||||||
ownerName: owner.firstName + ' ' + owner.lastName,
|
ownerName: owner ? owner.firstName + ' ' + owner.lastName : 'No owner (team project)',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user