diff --git a/packages/cli/src/environments.ee/source-control/__tests__/source-control.service.test.ts b/packages/cli/src/environments.ee/source-control/__tests__/source-control.service.test.ts index e562149fe1..e28ce455b7 100644 --- a/packages/cli/src/environments.ee/source-control/__tests__/source-control.service.test.ts +++ b/packages/cli/src/environments.ee/source-control/__tests__/source-control.service.test.ts @@ -139,6 +139,119 @@ describe('SourceControlService', () => { }); describe('getStatus', () => { + it('ensure updatedAt field for last deleted tag', async () => { + // ARRANGE + const user = mock(); + user.role = 'global:admin'; + + sourceControlImportService.getRemoteVersionIdsFromFiles.mockResolvedValue([]); + sourceControlImportService.getLocalVersionIdsFromDb.mockResolvedValue([]); + sourceControlImportService.getRemoteCredentialsFromFiles.mockResolvedValue([]); + sourceControlImportService.getLocalCredentialsFromDb.mockResolvedValue([]); + sourceControlImportService.getRemoteVariablesFromFile.mockResolvedValue([]); + sourceControlImportService.getLocalVariablesFromDb.mockResolvedValue([]); + + tagRepository.find.mockResolvedValue([]); + + // Define a tag that does only exist remotely. + // Pushing this means it was deleted. + sourceControlImportService.getRemoteTagsAndMappingsFromFile.mockResolvedValue({ + tags: [ + { + id: 'tag-id', + name: 'some name', + } as TagEntity, + ], + mappings: [], + }); + sourceControlImportService.getLocalTagsAndMappingsFromDb.mockResolvedValue({ + tags: [], + mappings: [], + }); + + folderRepository.find.mockResolvedValue([]); + sourceControlImportService.getRemoteFoldersAndMappingsFromFile.mockResolvedValue({ + folders: [], + }); + sourceControlImportService.getLocalFoldersAndMappingsFromDb.mockResolvedValue({ + folders: [], + }); + + // ACT + const pushResult = await sourceControlService.getStatus(user, { + direction: 'push', + verbose: false, + preferLocalVersion: false, + }); + + // ASSERT + + if (!Array.isArray(pushResult)) { + fail('Expected pushResult to be an array.'); + } + + expect(pushResult).toHaveLength(1); + expect(pushResult.find((i) => i.type === 'tags')?.updatedAt).toBeDefined(); + }); + + it('ensure updatedAt field for last deleted folder', async () => { + // ARRANGE + const user = mock(); + user.role = 'global:admin'; + + sourceControlImportService.getRemoteVersionIdsFromFiles.mockResolvedValue([]); + sourceControlImportService.getLocalVersionIdsFromDb.mockResolvedValue([]); + sourceControlImportService.getRemoteCredentialsFromFiles.mockResolvedValue([]); + sourceControlImportService.getLocalCredentialsFromDb.mockResolvedValue([]); + sourceControlImportService.getRemoteVariablesFromFile.mockResolvedValue([]); + sourceControlImportService.getLocalVariablesFromDb.mockResolvedValue([]); + + tagRepository.find.mockResolvedValue([]); + sourceControlImportService.getRemoteTagsAndMappingsFromFile.mockResolvedValue({ + tags: [], + mappings: [], + }); + sourceControlImportService.getLocalTagsAndMappingsFromDb.mockResolvedValue({ + tags: [], + mappings: [], + }); + + // Define a folder that does only exist remotely. + // Pushing this means it was deleted. + folderRepository.find.mockResolvedValue([]); + sourceControlImportService.getRemoteFoldersAndMappingsFromFile.mockResolvedValue({ + folders: [ + { + id: 'test-folder', + name: 'test folder name', + homeProjectId: 'some-id', + parentFolderId: null, + createdAt: '', + updatedAt: '', + }, + ], + }); + sourceControlImportService.getLocalFoldersAndMappingsFromDb.mockResolvedValue({ + folders: [], + }); + + // ACT + const pushResult = await sourceControlService.getStatus(user, { + direction: 'push', + verbose: false, + preferLocalVersion: false, + }); + + // ASSERT + + if (!Array.isArray(pushResult)) { + fail('Expected pushResult to be an array.'); + } + + expect(pushResult).toHaveLength(1); + expect(pushResult.find((i) => i.type === 'folders')?.updatedAt).toBeDefined(); + }); + it('conflict depends on the value of `direction`', async () => { // ARRANGE const user = mock(); diff --git a/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts index 2ae2eebd96..2626897c34 100644 --- a/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts @@ -869,6 +869,8 @@ export class SourceControlService { select: ['updatedAt'], }); + const lastUpdatedDate = lastUpdatedTag[0]?.updatedAt ?? new Date(); + const tagMappingsRemote = await this.sourceControlImportService.getRemoteTagsAndMappingsFromFile(context); const tagMappingsLocal = @@ -916,7 +918,7 @@ export class SourceControlService { location: options.direction === 'push' ? 'local' : 'remote', conflict: false, file: getTagsPath(this.gitFolder), - updatedAt: lastUpdatedTag[0]?.updatedAt.toISOString(), + updatedAt: lastUpdatedDate.toISOString(), }); }); tagsMissingInRemote.forEach((item) => { @@ -928,7 +930,7 @@ export class SourceControlService { location: options.direction === 'push' ? 'local' : 'remote', conflict: options.direction === 'push' ? false : true, file: getTagsPath(this.gitFolder), - updatedAt: lastUpdatedTag[0]?.updatedAt.toISOString(), + updatedAt: lastUpdatedDate.toISOString(), }); }); @@ -941,7 +943,7 @@ export class SourceControlService { location: options.direction === 'push' ? 'local' : 'remote', conflict: true, file: getTagsPath(this.gitFolder), - updatedAt: lastUpdatedTag[0]?.updatedAt.toISOString(), + updatedAt: lastUpdatedDate.toISOString(), }); }); @@ -1028,7 +1030,7 @@ export class SourceControlService { location: options.direction === 'push' ? 'local' : 'remote', conflict: true, file: getFoldersPath(this.gitFolder), - updatedAt: lastUpdatedFolder[0]?.updatedAt.toISOString(), + updatedAt: lastUpdatedDate.toISOString(), }); }); diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index bb5a82bf30..027ae85cc5 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -1,4 +1,4 @@ -import { inDevelopment, inProduction, LicenseState } from '@n8n/backend-common'; +import { inDevelopment, inProduction } from '@n8n/backend-common'; import { SecurityConfig } from '@n8n/config'; import { Time } from '@n8n/constants'; import type { APIRequest } from '@n8n/db'; @@ -77,7 +77,6 @@ export class Server extends AbstractServer { private readonly postHogClient: PostHogClient, private readonly eventService: EventService, private readonly instanceSettings: InstanceSettings, - private readonly licenseState: LicenseState, ) { super();