n8n/packages/cli/test/integration/public-api/variables.test.ts
n8n-assistant[bot] 2d9a2ec76e
chore: Bundle 2026-W9 (#27532)
Co-authored-by: Matsu <matias.huhta@n8n.io>
Co-authored-by: Dimitri Lavrenük <20122620+dlavrenuek@users.noreply.github.com>
Co-authored-by: Charlie Kolb <charlie@n8n.io>
Co-authored-by: RomanDavydchuk <roman.davydchuk@n8n.io>
Co-authored-by: Jaakko Husso <jaakko@n8n.io>
Co-authored-by: Dawid Myslak <dawid.myslak@gmail.com>
Co-authored-by: Svetoslav Dekov <svetoslav.dekov@n8n.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Guillaume Jacquart <jacquart.guillaume@gmail.com>
Co-authored-by: Sandra Zollner <sandra.zollner@n8n.io>
Co-authored-by: Milorad FIlipović <milorad@n8n.io>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Ricardo Espinoza <ricardo@n8n.io>
2026-03-25 07:51:06 +00:00

328 lines
8.6 KiB
TypeScript

import { createTeamProject, linkUserToProject, testDb } from '@n8n/backend-test-utils';
import type { Project, User, Variables } from '@n8n/db';
import { createMemberWithApiKey, createOwnerWithApiKey } from '@test-integration/db/users';
import {
createProjectVariable,
createVariable,
getVariableByIdOrFail,
} from '@test-integration/db/variables';
import { setupTestServer } from '@test-integration/utils';
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
describe('Variables in Public API', () => {
let owner: User;
let project: Project;
const testServer = setupTestServer({ endpointGroups: ['publicApi'] });
const licenseErrorMessage = new FeatureNotLicensedError('feat:variables').message;
beforeAll(async () => {
await testDb.init();
});
beforeEach(async () => {
await testDb.truncate(['Variables', 'User']);
owner = await createOwnerWithApiKey();
project = await createTeamProject();
});
describe('GET /variables', () => {
it('if licensed, should return all variables with pagination', async () => {
/**
* Arrange
*/
testServer.license.enable('feat:variables');
const variables = await Promise.all([
createVariable(),
createVariable(),
createVariable(),
createProjectVariable('projectKey', 'projectValue', project),
]);
/**
* Act
*/
const response = await testServer.publicApiAgentFor(owner).get('/variables');
/**
* Assert
*/
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('data');
expect(response.body).toHaveProperty('nextCursor');
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBe(variables.length);
variables.forEach(({ id, key, value, project }) => {
expect(response.body.data).toContainEqual(expect.objectContaining({ id, key, value }));
if (project) {
const projectResponse = response.body.data.find((v: Variables) => v.id === id).project;
expect(projectResponse).toBeDefined();
expect(projectResponse).toEqual(
expect.objectContaining({ id: project.id, name: project.name }),
);
}
});
});
it('if licensed, should be able to filter variables by projectId and state', async () => {
/**
* Arrange
*/
testServer.license.enable('feat:variables');
await Promise.all([
createVariable(),
createProjectVariable('projectKey', 'projectValue', project),
createProjectVariable('emptyVar', '', project),
createVariable('emptyVar', ''),
]);
/**
* Act
*/
const response = await testServer
.publicApiAgentFor(owner)
.get('/variables')
.query({ projectId: project.id, state: 'empty' });
/**
* Assert
*/
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('data');
expect(response.body).toHaveProperty('nextCursor');
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBe(1);
expect(response.body.data[0]).toEqual(
expect.objectContaining({
key: 'emptyVar',
value: '',
project: expect.objectContaining({ id: project.id }),
}),
);
});
it('if not licensed, should reject', async () => {
/**
* Act
*/
const response = await testServer.publicApiAgentFor(owner).get('/variables');
/**
* Assert
*/
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message', licenseErrorMessage);
});
it('should not return variables from projects the user is not a member of', async () => {
/**
* Arrange
*/
testServer.license.enable('feat:variables');
const member = await createMemberWithApiKey();
const memberProject = await createTeamProject('Member Project');
await linkUserToProject(member, memberProject, 'project:admin');
const otherProject = await createTeamProject('Other Project');
const memberVar = await createProjectVariable('memberKey', 'memberValue', memberProject);
await createProjectVariable('secretKey', 'secretValue', otherProject);
/**
* Act
*/
const allResponse = await testServer.publicApiAgentFor(member).get('/variables');
/**
* Assert
*/
expect(allResponse.status).toBe(200);
const returnedIds = allResponse.body.data.map((v: Variables) => v.id);
expect(returnedIds).toContain(memberVar.id);
expect(allResponse.body.data).toHaveLength(1);
/**
* Act
*/
const crossProjectResponse = await testServer
.publicApiAgentFor(member)
.get('/variables')
.query({ projectId: otherProject.id });
/**
* Assert
*/
expect(crossProjectResponse.status).toBe(200);
expect(crossProjectResponse.body.data).toHaveLength(0);
});
});
describe('POST /variables', () => {
it('if licensed, should create a new variable', async () => {
/**
* Arrange
*/
testServer.license.enable('feat:variables');
const variablePayload = { key: 'key', value: 'value' };
/**
* Act
*/
const response = await testServer
.publicApiAgentFor(owner)
.post('/variables')
.send(variablePayload);
/**
* Assert
*/
expect(response.status).toBe(201);
await expect(getVariableByIdOrFail(response.body.id)).resolves.toEqual(
expect.objectContaining(variablePayload),
);
});
it('if licensed, should create a variable linked to a project', async () => {
/**
* Arrange
*/
testServer.license.enable('feat:variables');
const variablePayload = { key: 'key', value: 'value', projectId: project.id };
/**
* Act
*/
const response = await testServer
.publicApiAgentFor(owner)
.post('/variables')
.send(variablePayload);
/**
* Assert
*/
expect(response.status).toBe(201);
await expect(getVariableByIdOrFail(response.body.id)).resolves.toEqual(
expect.objectContaining({
key: 'key',
value: 'value',
project: expect.objectContaining({ id: project.id }),
}),
);
});
it('if not licensed, should reject', async () => {
/**
* Arrange
*/
const variablePayload = { key: 'key', value: 'value' };
/**
* Act
*/
const response = await testServer
.publicApiAgentFor(owner)
.post('/variables')
.send(variablePayload);
/**
* Assert
*/
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message', licenseErrorMessage);
});
});
describe('PUT /variables/:id', () => {
const variablePayload = { key: 'updatedKey', value: 'updatedValue' };
let variable: Variables;
beforeEach(async () => {
variable = await createVariable();
});
it('if licensed, should update a variable', async () => {
testServer.license.enable('feat:variables');
const response = await testServer
.publicApiAgentFor(owner)
.put(`/variables/${variable.id}`)
.send(variablePayload);
expect(response.status).toBe(204);
const updatedVariable = await getVariableByIdOrFail(variable.id);
expect(updatedVariable).toEqual(expect.objectContaining(variablePayload));
});
it('if licensed, should update a variable to link it to a project', async () => {
testServer.license.enable('feat:variables');
const response = await testServer
.publicApiAgentFor(owner)
.put(`/variables/${variable.id}`)
.send({ ...variablePayload, projectId: project.id });
expect(response.status).toBe(204);
const updatedVariable = await getVariableByIdOrFail(variable.id);
expect(updatedVariable).toEqual(
expect.objectContaining({
...variablePayload,
project: expect.objectContaining({ id: project.id }),
}),
);
});
it('if not licensed, should reject', async () => {
const response = await testServer
.publicApiAgentFor(owner)
.put(`/variables/${variable.id}`)
.send(variablePayload);
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message', licenseErrorMessage);
});
});
describe('DELETE /variables/:id', () => {
let variable: Variables;
beforeEach(async () => {
variable = await createVariable();
});
it('if licensed, should delete a variable', async () => {
/**
* Arrange
*/
testServer.license.enable('feat:variables');
/**
* Act
*/
const response = await testServer
.publicApiAgentFor(owner)
.delete(`/variables/${variable.id}`);
/**
* Assert
*/
expect(response.status).toBe(204);
await expect(getVariableByIdOrFail(variable.id)).rejects.toThrow();
});
it('if not licensed, should reject', async () => {
/**
* Act
*/
const response = await testServer
.publicApiAgentFor(owner)
.delete(`/variables/${variable.id}`);
/**
* Assert
*/
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message', licenseErrorMessage);
});
});
});