mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(core): Add endpoint to create free AI credits (#12362)
This commit is contained in:
99
packages/cli/test/integration/ai/ai.api.test.ts
Normal file
99
packages/cli/test/integration/ai/ai.api.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
import { FREE_AI_CREDITS_CREDENTIAL_NAME, OPEN_AI_API_CREDENTIAL_TYPE } from '@/constants';
|
||||
import type { Project } from '@/databases/entities/project';
|
||||
import type { User } from '@/databases/entities/user';
|
||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
import { AiService } from '@/services/ai.service';
|
||||
|
||||
import { createOwner } from '../shared/db/users';
|
||||
import * as testDb from '../shared/test-db';
|
||||
import type { SuperAgentTest } from '../shared/types';
|
||||
import { setupTestServer } from '../shared/utils';
|
||||
|
||||
const createAiCreditsResponse = {
|
||||
apiKey: randomUUID(),
|
||||
url: 'https://api.openai.com',
|
||||
};
|
||||
|
||||
Container.set(
|
||||
AiService,
|
||||
mock<AiService>({
|
||||
createFreeAiCredits: async () => createAiCreditsResponse,
|
||||
}),
|
||||
);
|
||||
|
||||
const testServer = setupTestServer({ endpointGroups: ['ai'] });
|
||||
|
||||
let owner: User;
|
||||
let ownerPersonalProject: Project;
|
||||
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||
|
||||
owner = await createOwner();
|
||||
|
||||
ownerPersonalProject = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(
|
||||
owner.id,
|
||||
);
|
||||
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
});
|
||||
|
||||
describe('POST /ai/free-credits', () => {
|
||||
test('should create OpenAI managed credential', async () => {
|
||||
// Act
|
||||
const response = await authOwnerAgent.post('/ai/free-credits').send({
|
||||
projectId: ownerPersonalProject.id,
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const { id, name, type, data: encryptedData, scopes } = response.body.data;
|
||||
|
||||
expect(name).toBe(FREE_AI_CREDITS_CREDENTIAL_NAME);
|
||||
expect(type).toBe(OPEN_AI_API_CREDENTIAL_TYPE);
|
||||
expect(encryptedData).not.toBe(createAiCreditsResponse);
|
||||
|
||||
expect(scopes).toEqual(
|
||||
[
|
||||
'credential:create',
|
||||
'credential:delete',
|
||||
'credential:list',
|
||||
'credential:move',
|
||||
'credential:read',
|
||||
'credential:share',
|
||||
'credential:update',
|
||||
].sort(),
|
||||
);
|
||||
|
||||
const credential = await Container.get(CredentialsRepository).findOneByOrFail({ id });
|
||||
|
||||
expect(credential.name).toBe(FREE_AI_CREDITS_CREDENTIAL_NAME);
|
||||
expect(credential.type).toBe(OPEN_AI_API_CREDENTIAL_TYPE);
|
||||
expect(credential.data).not.toBe(createAiCreditsResponse);
|
||||
expect(credential.isManaged).toBe(true);
|
||||
|
||||
const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({
|
||||
relations: { project: true, credentials: true },
|
||||
where: { credentialsId: credential.id },
|
||||
});
|
||||
|
||||
expect(sharedCredential.project.id).toBe(ownerPersonalProject.id);
|
||||
expect(sharedCredential.credentials.name).toBe(FREE_AI_CREDITS_CREDENTIAL_NAME);
|
||||
expect(sharedCredential.credentials.isManaged).toBe(true);
|
||||
|
||||
const user = await Container.get(UserRepository).findOneOrFail({ where: { id: owner.id } });
|
||||
|
||||
expect(user.settings?.userClaimedAiCredits).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -87,6 +87,7 @@ describe('GET /credentials', () => {
|
||||
validateMainCredentialData(credential);
|
||||
expect('data' in credential).toBe(false);
|
||||
expect(savedCredentialsIds).toContain(credential.id);
|
||||
expect('isManaged' in credential).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1035,6 +1036,19 @@ describe('PATCH /credentials/:id', () => {
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
||||
|
||||
test('should fail with a 400 is credential is managed', async () => {
|
||||
const { id } = await saveCredential(randomCredentialPayload({ isManaged: true }), {
|
||||
user: owner,
|
||||
role: 'credential:owner',
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.patch(`/credentials/${id}`)
|
||||
.send(randomCredentialPayload());
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /credentials/new', () => {
|
||||
@@ -1188,10 +1202,11 @@ const INVALID_PAYLOADS = [
|
||||
];
|
||||
|
||||
function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) {
|
||||
const { name, type, sharedWithProjects, homeProject } = credential;
|
||||
const { name, type, sharedWithProjects, homeProject, isManaged } = credential;
|
||||
|
||||
expect(typeof name).toBe('string');
|
||||
expect(typeof type).toBe('string');
|
||||
expect(typeof isManaged).toBe('boolean');
|
||||
|
||||
if (sharedWithProjects) {
|
||||
expect(Array.isArray(sharedWithProjects)).toBe(true);
|
||||
|
||||
@@ -37,10 +37,13 @@ const randomTopLevelDomain = () => chooseRandomly(POPULAR_TOP_LEVEL_DOMAINS);
|
||||
|
||||
export const randomName = () => randomString(4, 8).toLowerCase();
|
||||
|
||||
export const randomCredentialPayload = (): CredentialPayload => ({
|
||||
export const randomCredentialPayload = ({
|
||||
isManaged = false,
|
||||
}: { isManaged?: boolean } = {}): CredentialPayload => ({
|
||||
name: randomName(),
|
||||
type: randomName(),
|
||||
data: { accessToken: randomString(6, 16) },
|
||||
isManaged,
|
||||
});
|
||||
|
||||
export const uniqueId = () => uuid();
|
||||
|
||||
@@ -42,7 +42,8 @@ type EndpointGroup =
|
||||
| 'role'
|
||||
| 'dynamic-node-parameters'
|
||||
| 'apiKeys'
|
||||
| 'evaluation';
|
||||
| 'evaluation'
|
||||
| 'ai';
|
||||
|
||||
export interface SetupProps {
|
||||
endpointGroups?: EndpointGroup[];
|
||||
@@ -68,6 +69,7 @@ export type CredentialPayload = {
|
||||
name: string;
|
||||
type: string;
|
||||
data: ICredentialDataDecryptedObject;
|
||||
isManaged?: boolean;
|
||||
};
|
||||
|
||||
export type SaveCredentialFunction = (
|
||||
|
||||
@@ -283,6 +283,9 @@ export const setupTestServer = ({
|
||||
await import('@/evaluation.ee/test-definitions.controller.ee');
|
||||
await import('@/evaluation.ee/test-runs.controller.ee');
|
||||
break;
|
||||
|
||||
case 'ai':
|
||||
await import('@/controllers/ai.controller');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user