fix: Always generate tags.json file when using environments (#19698)

This commit is contained in:
Irénée
2025-09-18 12:05:00 +01:00
committed by GitHub
parent 5764ef3cc6
commit dee22162f4
2 changed files with 49 additions and 13 deletions

View File

@@ -9,6 +9,8 @@ import type {
SharedCredentialsRepository,
SharedWorkflowRepository,
WorkflowRepository,
TagEntity,
WorkflowTagMapping,
} from '@n8n/db';
import { Container } from '@n8n/di';
import { mock, captor } from 'jest-mock-extended';
@@ -181,27 +183,62 @@ describe('SourceControlExportService', () => {
describe('exportTagsToWorkFolder', () => {
it('should export tags to work folder', async () => {
// Arrange
tagRepository.find.mockResolvedValue([mock()]);
workflowTagMappingRepository.find.mockResolvedValue([mock()]);
const mockTag = mock<TagEntity>({
id: 'tag1',
name: 'Tag 1',
createdAt: new Date(),
updatedAt: new Date(),
});
const mockWorkflow = mock<WorkflowTagMapping>({
tagId: 'tag1',
workflowId: 'workflow1',
});
tagRepository.find.mockResolvedValue([mockTag]);
workflowTagMappingRepository.find.mockResolvedValue([mockWorkflow]);
const fileName = '/mock/n8n/git/tags.json';
// Act
const result = await service.exportTagsToWorkFolder(globalAdminContext);
// Assert
expect(fsWriteFile).toHaveBeenCalledWith(
fileName,
JSON.stringify(
{
tags: [
{
id: mockTag.id,
name: mockTag.name,
},
],
mappings: [mockWorkflow],
},
null,
2,
),
);
expect(result.count).toBe(1);
expect(result.files).toHaveLength(1);
expect(result.files[0]).toMatchObject({ id: '', name: fileName });
});
it('should not export empty tags', async () => {
it('should clear tags file and export it when there are no tags', async () => {
// Arrange
tagRepository.find.mockResolvedValue([]);
const fileName = '/mock/n8n/git/tags.json';
// Act
const result = await service.exportTagsToWorkFolder(globalAdminContext);
// Assert
expect(fsWriteFile).toHaveBeenCalledWith(
fileName,
JSON.stringify({ tags: [], mappings: [] }, null, 2),
);
expect(result.count).toBe(0);
expect(result.files).toHaveLength(0);
expect(result.files).toHaveLength(1);
expect(result.files[0]).toMatchObject({ id: '', name: fileName });
});
});

View File

@@ -305,14 +305,17 @@ export class SourceControlExportService {
async exportTagsToWorkFolder(context: SourceControlContext): Promise<ExportResult> {
try {
const fileName = path.join(this.gitFolder, SOURCE_CONTROL_TAGS_EXPORT_FILE);
sourceControlFoldersExistCheck([this.gitFolder]);
const tags = await this.tagRepository.find();
// do not export empty tags
if (tags.length === 0) {
await fsWriteFile(fileName, JSON.stringify({ tags: [], mappings: [] }, null, 2));
return {
count: 0,
folder: this.gitFolder,
files: [],
files: [{ id: '', name: fileName }],
};
}
const mappingsOfAllowedWorkflows = await this.workflowTagMappingRepository.find({
@@ -321,11 +324,12 @@ export class SourceControlExportService {
context,
),
});
const allowedWorkflows = await this.workflowRepository.find({
where:
this.sourceControlScopedService.getWorkflowsInAdminProjectsFromContextFilter(context),
});
const fileName = path.join(this.gitFolder, SOURCE_CONTROL_TAGS_EXPORT_FILE);
const existingTagsAndMapping = await readTagAndMappingsFromSourceControlFile(fileName);
// keep all mappings that are not accessible by the current user
@@ -350,12 +354,7 @@ export class SourceControlExportService {
return {
count: tags.length,
folder: this.gitFolder,
files: [
{
id: '',
name: fileName,
},
],
files: [{ id: '', name: fileName }],
};
} catch (error) {
this.logger.error('Failed to export tags to work folder', { error });