Fix server integration tests 2 (#10818)

## Context
- Removing search* integration tests instead of fixing them because they
will be replaced by global search very soon
- Fixed billing + add missing seeds to make them work
- Fixed integration tests not using consistently the correct "test" db
- Fixed ci not running the with-db-reset configuration due to nx
configuration being used twice for different level of the command
- Enriched .env.test
- Fixed parts where exceptions were not thrown properly and not caught
by exception handler to convert to 400 when needed
- Refactored feature flag service that had 2 different implementations
in lab and admin panel + added tests
- Fixed race condition when migrations are created at the same timestamp
and doing the same type of operation, in this case object deletion could
break because table could be deleted earlier than its relations
- Fixed many integration tests that were not up to date since the CI has
been broken for a while

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
Weiko
2025-03-13 17:48:29 +01:00
committed by GitHub
parent d48b2b3264
commit fc30ba57f8
74 changed files with 492 additions and 2578 deletions
@@ -0,0 +1,268 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import {
FeatureFlagException,
FeatureFlagExceptionCode,
} from 'src/engine/core-modules/feature-flag/feature-flag.exception';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
import { publicFeatureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/is-public-feature-flag.validate';
jest.mock(
'src/engine/core-modules/feature-flag/validates/is-public-feature-flag.validate',
);
jest.mock(
'src/engine/core-modules/feature-flag/validates/feature-flag.validate',
);
describe('FeatureFlagService', () => {
let service: FeatureFlagService;
const mockFeatureFlagRepository = {
findOneBy: jest.fn(),
find: jest.fn(),
upsert: jest.fn(),
};
const workspaceId = 'workspace-id';
const featureFlag = FeatureFlagKey.IsWorkflowEnabled;
beforeEach(async () => {
jest.clearAllMocks();
(
publicFeatureFlagValidator.assertIsPublicFeatureFlag as jest.Mock
).mockReset();
(featureFlagValidator.assertIsFeatureFlagKey as jest.Mock).mockReset();
const module: TestingModule = await Test.createTestingModule({
providers: [
FeatureFlagService,
{
provide: getRepositoryToken(FeatureFlag, 'core'),
useValue: mockFeatureFlagRepository,
},
],
}).compile();
service = module.get<FeatureFlagService>(FeatureFlagService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('isFeatureEnabled', () => {
it('should return true when feature flag is enabled', async () => {
// Prepare
mockFeatureFlagRepository.findOneBy.mockResolvedValue({
key: featureFlag,
value: true,
workspaceId,
});
// Act
const result = await service.isFeatureEnabled(featureFlag, workspaceId);
// Assert
expect(result).toBe(true);
expect(mockFeatureFlagRepository.findOneBy).toHaveBeenCalledWith({
workspaceId,
key: featureFlag,
value: true,
});
});
it('should return false when feature flag is not found', async () => {
// Prepare
mockFeatureFlagRepository.findOneBy.mockResolvedValue(null);
// Act
const result = await service.isFeatureEnabled(featureFlag, workspaceId);
// Assert
expect(result).toBe(false);
});
it('should return false when feature flag value is false', async () => {
// Prepare
mockFeatureFlagRepository.findOneBy.mockResolvedValue({
key: featureFlag,
value: false,
workspaceId,
});
// Act
const result = await service.isFeatureEnabled(featureFlag, workspaceId);
// Assert
expect(result).toBe(false);
});
});
describe('getWorkspaceFeatureFlags', () => {
it('should return all feature flags for a workspace', async () => {
// Prepare
const mockFeatureFlags = [
{ key: FeatureFlagKey.IsWorkflowEnabled, value: true, workspaceId },
{ key: FeatureFlagKey.IsCopilotEnabled, value: false, workspaceId },
];
mockFeatureFlagRepository.find.mockResolvedValue(mockFeatureFlags);
// Act
const result = await service.getWorkspaceFeatureFlags(workspaceId);
// Assert
expect(result).toEqual(mockFeatureFlags);
expect(mockFeatureFlagRepository.find).toHaveBeenCalledWith({
where: { workspaceId },
});
});
});
describe('getWorkspaceFeatureFlagsMap', () => {
it('should return a map of feature flags for a workspace', async () => {
// Prepare
const mockFeatureFlags = [
{ key: FeatureFlagKey.IsWorkflowEnabled, value: true, workspaceId },
{ key: FeatureFlagKey.IsCopilotEnabled, value: false, workspaceId },
];
mockFeatureFlagRepository.find.mockResolvedValue(mockFeatureFlags);
// Act
const result = await service.getWorkspaceFeatureFlagsMap(workspaceId);
// Assert
expect(result).toEqual({
[FeatureFlagKey.IsWorkflowEnabled]: true,
[FeatureFlagKey.IsCopilotEnabled]: false,
});
});
});
describe('enableFeatureFlags', () => {
it('should enable multiple feature flags for a workspace', async () => {
// Prepare
const keys = [
FeatureFlagKey.IsWorkflowEnabled,
FeatureFlagKey.IsCopilotEnabled,
];
mockFeatureFlagRepository.upsert.mockResolvedValue({});
// Act
await service.enableFeatureFlags(keys, workspaceId);
// Assert
expect(mockFeatureFlagRepository.upsert).toHaveBeenCalledWith(
keys.map((key) => ({ workspaceId, key, value: true })),
{
conflictPaths: ['workspaceId', 'key'],
skipUpdateIfNoValuesChanged: true,
},
);
});
});
describe('upsertWorkspaceFeatureFlag', () => {
it('should upsert a feature flag for a workspace', async () => {
// Prepare
const value = true;
const mockFeatureFlag = {
key: featureFlag,
value,
workspaceId,
};
mockFeatureFlagRepository.upsert.mockResolvedValue({
generatedMaps: [mockFeatureFlag],
});
(
featureFlagValidator.assertIsFeatureFlagKey as jest.Mock
).mockImplementation(() => true);
// Act
const result = await service.upsertWorkspaceFeatureFlag({
workspaceId,
featureFlag,
value,
});
// Assert
expect(result).toEqual(mockFeatureFlag);
expect(mockFeatureFlagRepository.upsert).toHaveBeenCalledWith(
{
key: FeatureFlagKey[featureFlag],
value,
workspaceId,
},
{
conflictPaths: ['workspaceId', 'key'],
skipUpdateIfNoValuesChanged: true,
},
);
});
it('should throw an exception when feature flag key is invalid', async () => {
// Prepare
const invalidFeatureFlag = 'INVALID_KEY' as FeatureFlagKey;
const value = true;
(
featureFlagValidator.assertIsFeatureFlagKey as jest.Mock
).mockImplementation(() => {
throw new FeatureFlagException(
'Invalid feature flag key',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
);
});
// Act & Assert
await expect(
service.upsertWorkspaceFeatureFlag({
workspaceId,
featureFlag: invalidFeatureFlag,
value,
}),
).rejects.toThrow(
new FeatureFlagException(
'Invalid feature flag key',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
),
);
});
it('should throw an exception when non-public feature flag is used with shouldBePublic=true', async () => {
// Prepare
(
publicFeatureFlagValidator.assertIsPublicFeatureFlag as jest.Mock
).mockImplementation(() => {
throw new FeatureFlagException(
'Invalid feature flag key, flag is not public',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
);
});
// Act & Assert
await expect(
service.upsertWorkspaceFeatureFlag({
workspaceId,
featureFlag,
value: true,
shouldBePublic: true,
}),
).rejects.toThrow(
new FeatureFlagException(
'Invalid feature flag key, flag is not public',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
),
);
});
});
});
@@ -7,6 +7,12 @@ import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import {
FeatureFlagException,
FeatureFlagExceptionCode,
} from 'src/engine/core-modules/feature-flag/feature-flag.exception';
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
import { publicFeatureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/is-public-feature-flag.validate';
@Injectable()
export class FeatureFlagService {
@@ -64,4 +70,48 @@ export class FeatureFlagService {
},
);
}
public async upsertWorkspaceFeatureFlag({
workspaceId,
featureFlag,
value,
shouldBePublic = false,
}: {
workspaceId: string;
featureFlag: FeatureFlagKey;
value: boolean;
shouldBePublic?: boolean;
}): Promise<FeatureFlag> {
if (shouldBePublic) {
publicFeatureFlagValidator.assertIsPublicFeatureFlag(
featureFlag,
new FeatureFlagException(
'Invalid feature flag key, flag is not public',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
),
);
}
featureFlagValidator.assertIsFeatureFlagKey(
featureFlag,
new FeatureFlagException(
'Invalid feature flag key',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
),
);
const upsertResult = await this.featureFlagRepository.upsert(
{
key: FeatureFlagKey[featureFlag],
value,
workspaceId: workspaceId,
},
{
conflictPaths: ['workspaceId', 'key'],
skipUpdateIfNoValuesChanged: true,
},
);
return upsertResult.generatedMaps[0] as FeatureFlag;
}
}