fix: move script api to enterprise

This commit is contained in:
DarkPhoenix2704
2025-12-04 07:08:00 +00:00
parent 737bcc9c67
commit 7aac1bf84c
3 changed files with 758 additions and 0 deletions

View File

@@ -56,6 +56,8 @@ export enum PlanFeatureTypes {
FEATURE_TABLE_AND_FIELD_PERMISSIONS = 'feature_table_and_field_permissions',
FEATURE_PRIVATE_BASES = 'feature_private_bases',
FEATURE_API_MEMBER_MANAGEMENT = 'feature_api_member_management',
FEATURE_TEAM_MANAGEMENT = 'feature_team_management',
FEATURE_API_SCRIPT_MANAGEMENT = 'feature_api_script_management',
FEATURE_API_VIEW_V3 = 'feature_api_view_v3',
FEATURE_CALENDAR_RANGE = 'feature_calendar_range',
FEATURE_AI_PROMPT_FIELD = 'feature_ai_prompt_field',
@@ -251,6 +253,7 @@ export const PlanFeatureUpgradeMessages: Record<PlanFeatureTypes, string> = {
[PlanFeatureTypes.FEATURE_API_MEMBER_MANAGEMENT]:
'to use member management api.',
[PlanFeatureTypes.FEATURE_API_VIEW_V3]: 'to use view api.',
[PlanFeatureTypes.FEATURE_API_SCRIPT_MANAGEMENT]: 'to use script api.',
[PlanFeatureTypes.FEATURE_CALENDAR_RANGE]:
'to visualize records in a calendar range.',
[PlanFeatureTypes.FEATURE_AI_PROMPT_FIELD]: 'to use AI text fields.',

View File

@@ -8553,6 +8553,406 @@
"workspace-level-commenter",
"workspace-level-no-access"
]
},
"WorkspaceTeamCreate": {
"type": "object",
"description": "Create workspace team request for v3 API",
"properties": {
"team_id": {
"type": "string",
"description": "Team ID to add to workspace",
"example": "team123"
},
"workspace_role": {
"type": "string",
"description": "Workspace role to assign to the team (creator or lower only)",
"enum": [
"workspace-level-creator",
"workspace-level-editor",
"workspace-level-viewer",
"workspace-level-commenter",
"workspace-level-no-access"
],
"example": "workspace-level-editor"
}
},
"required": [
"team_id",
"workspace_role"
]
},
"WorkspaceTeamCreateBulk": {
"type": "object",
"description": "Bulk create workspace team requests for v3 API",
"properties": {
"teams": {
"type": "array",
"description": "Array of teams to add to workspace",
"items": {
"$ref": "#/components/schemas/WorkspaceTeamCreate"
},
"minItems": 1,
"example": [
{
"team_id": "team123",
"workspace_role": "workspace-level-editor"
},
{
"team_id": "team456",
"workspace_role": "workspace-level-viewer"
}
]
}
},
"required": [
"teams"
]
},
"WorkspaceTeamUpdate": {
"type": "object",
"description": "Update workspace team request for v3 API",
"properties": {
"team_id": {
"type": "string",
"description": "Team ID to update in workspace",
"example": "team123"
},
"workspace_role": {
"type": "string",
"description": "New workspace role for the team (creator or lower only)",
"enum": [
"workspace-level-creator",
"workspace-level-editor",
"workspace-level-viewer",
"workspace-level-commenter",
"workspace-level-no-access"
],
"example": "workspace-level-viewer"
}
},
"required": [
"team_id",
"workspace_role"
]
},
"WorkspaceTeamDelete": {
"type": "object",
"description": "Delete workspace team request for v3 API",
"properties": {
"team_id": {
"type": "string",
"description": "Team ID to remove from workspace",
"example": "team123"
}
},
"required": [
"team_id"
]
},
"WorkspaceTeamDeleteBulk": {
"type": "object",
"description": "Bulk delete workspace team requests for v3 API",
"properties": {
"teams": {
"type": "array",
"description": "Array of teams to remove from workspace",
"items": {
"$ref": "#/components/schemas/WorkspaceTeamDelete"
},
"minItems": 1,
"example": [
{
"team_id": "team123"
},
{
"team_id": "team456"
}
]
}
},
"required": [
"teams"
]
},
"BaseTeamCreate": {
"type": "object",
"description": "Create base team request for v3 API",
"properties": {
"team_id": {
"type": "string",
"description": "Team ID to add to base",
"example": "team123"
},
"base_role": {
"type": "string",
"description": "Base role to assign to the team (creator or lower only)",
"enum": [
"creator",
"editor",
"viewer",
"commenter",
"no-access"
],
"example": "editor"
}
},
"required": [
"team_id",
"base_role"
]
},
"BaseTeamCreateBulk": {
"type": "object",
"description": "Bulk create base team requests for v3 API",
"properties": {
"teams": {
"type": "array",
"description": "Array of teams to add to base",
"items": {
"$ref": "#/components/schemas/BaseTeamCreate"
},
"minItems": 1,
"example": [
{
"team_id": "team123",
"base_role": "editor"
},
{
"team_id": "team456",
"base_role": "viewer"
}
]
}
},
"required": [
"teams"
]
},
"BaseTeamUpdate": {
"type": "object",
"description": "Update base team request for v3 API",
"properties": {
"team_id": {
"type": "string",
"description": "Team ID to update in base",
"example": "team123"
},
"base_role": {
"type": "string",
"description": "New base role for the team (creator or lower only)",
"enum": [
"creator",
"editor",
"viewer",
"commenter",
"no-access"
],
"example": "viewer"
}
},
"required": [
"team_id",
"base_role"
]
},
"BaseTeamDelete": {
"type": "object",
"description": "Delete base team request for v3 API",
"properties": {
"team_id": {
"type": "string",
"description": "Team ID to remove from base",
"example": "team123"
}
},
"required": [
"team_id"
]
},
"BaseTeamDeleteBulk": {
"type": "object",
"description": "Bulk delete base team requests for v3 API",
"properties": {
"teams": {
"type": "array",
"description": "Array of teams to remove from base",
"items": {
"$ref": "#/components/schemas/BaseTeamDelete"
},
"minItems": 1,
"example": [
{
"team_id": "team123"
},
{
"team_id": "team456"
}
]
}
},
"required": [
"teams"
]
},
"ScriptList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the script."
},
"title": {
"type": "string",
"description": "Title of the script."
},
"description": {
"type": [
"string",
"null"
],
"description": "Description of the script."
},
"base_id": {
"type": "string",
"description": "Unique identifier for the base to which this script belongs to."
},
"workspace_id": {
"type": "string",
"description": "Unique identifier for the workspace to which this base belongs to."
}
},
"required": [
"id",
"title",
"base_id",
"workspace_id"
]
}
}
},
"required": [
"list"
]
},
"ScriptCreateReq": {
"description": "Script create request body",
"type": "object",
"required": [
"title"
],
"properties": {
"title": {
"type": "string",
"description": "Title of the script."
},
"description": {
"type": ["string", "null"],
"description": "Description of the script."
},
"script": {
"type": "string",
"description": "Script content."
},
"config": {
"type": "object",
"description": "Configuration for the script."
},
"meta": {
"type": "object",
"description": "Metadata for the script."
}
}
},
"ScriptUpdateReq": {
"description": "Script update request body",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Title of the script."
},
"description": {
"type": ["string", "null"],
"description": "Description of the script."
},
"script": {
"type": "string",
"description": "Script content."
},
"config": {
"type": "object",
"description": "Configuration for the script."
},
"meta": {
"type": "object",
"description": "Metadata for the script."
}
}
},
"ScriptGetResponse": {
"description": "Script with additional info",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the script"
},
"title": {
"type": "string",
"description": "Title of the script."
},
"description": {
"type": [
"string",
"null"
],
"description": "Description of the script."
},
"script": {
"type": "string",
"description": "Script content."
},
"config": {
"type": "object",
"description": "Configuration for the script."
},
"meta": {
"type": "object",
"description": "Metadata of the script."
},
"base_id": {
"type": "string",
"description": "Unique identifier for the base"
},
"workspace_id": {
"type": "string",
"description": "Unique identifier for the workspace"
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the script was created"
},
"updated_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the script was last updated"
}
},
"required": [
"id",
"title",
"script",
"base_id",
"workspace_id",
"created_at",
"updated_at"
]
}
},
"responses": {

View File

@@ -0,0 +1,355 @@
import 'mocha';
import { expect } from 'chai';
import request from 'supertest';
import { PlanFeatureTypes } from 'nocodb-sdk';
import { isEE } from '../../../utils/helpers';
import init from '../../../init';
import { overrideFeature } from '../../../utils/plan.utils';
interface CreateScriptArgs {
title: string;
description?: string;
script: string;
config?: any;
}
export default function () {
if (!isEE()) {
return true;
}
describe(`Scripts v3`, () => {
let context: Awaited<ReturnType<typeof init>>;
let initBase: any;
let API_PREFIX: string;
let featureMock: any;
async function _createScript(args: CreateScriptArgs) {
const response = await request(context.app)
.post(`${API_PREFIX}/scripts`)
.set('xc-auth', context.token)
.send(args)
.expect(201);
return response.body;
}
async function _verifyScript(script: any, args: CreateScriptArgs) {
expect(script).to.be.an('object');
const requiredFields = [
'id',
'title',
'base_id',
'workspace_id',
'script',
'created_at',
'updated_at',
...(args.description ? ['description'] : []),
];
expect(Object.keys(script)).to.include.members(requiredFields);
expect(script.id).to.be.a('string');
expect(script.title).to.equal(args.title);
if (args.description) {
expect(script.description).to.equal(args.description);
}
expect(script.script).to.equal(args.script);
expect(script.base_id).to.equal(initBase.id);
expect(script.workspace_id).to.be.a('string');
}
const scriptData: CreateScriptArgs = {
title: 'Test Script',
description: 'A test script for unit testing',
script: 'console.log("Hello World");',
config: { timeout: 30000 },
};
beforeEach(async () => {
context = await init();
const workspaceId = context.fk_workspace_id;
featureMock = await overrideFeature({
workspace_id: context.fk_workspace_id,
feature: `${PlanFeatureTypes.FEATURE_API_SCRIPT_MANAGEMENT}`,
allowed: true,
});
const baseResult = await request(context.app)
.post(`/api/v3/meta/workspaces/${workspaceId}/bases`)
.set('xc-token', context.xc_token)
.send({
title: 'ScriptTestBase',
})
.expect(200);
initBase = baseResult.body;
API_PREFIX = `/api/v3/meta/bases/${initBase.id}`;
});
afterEach(async () => {
await featureMock?.restore?.();
});
it('List Scripts v3', async () => {
// Create multiple scripts
await _createScript(scriptData);
await _createScript({
title: 'Script 2',
description: 'Second script',
script: 'console.log("Script 2");',
});
await _createScript({
title: 'Script 3',
description: 'Third script',
script: 'console.log("Script 3");',
});
// List scripts
const listResponse = await request(context.app)
.get(`${API_PREFIX}/scripts`)
.set('xc-auth', context.token)
.expect(200);
const scripts = listResponse.body.list;
expect(scripts).to.be.an('array').that.is.not.empty;
expect(scripts).to.have.lengthOf(3);
// Verify first script in list
const firstScript = scripts.find((s) => s.title === 'Test Script');
expect(firstScript).to.exist;
expect(firstScript).to.have.property('id');
expect(firstScript).to.have.property('title', 'Test Script');
expect(firstScript).to.have.property(
'description',
'A test script for unit testing',
);
expect(firstScript).to.have.property('base_id', initBase.id);
expect(firstScript).to.have.property('workspace_id');
// List response should not include script content, config
expect(firstScript).to.not.have.property('script');
expect(firstScript).to.not.have.property('config');
});
it('Create Script v3', async () => {
const scriptObj = await _createScript(scriptData);
await _verifyScript(scriptObj, scriptData);
// Minimal arguments
const minimalData: CreateScriptArgs = {
title: 'Minimal Script',
script: 'console.log("minimal");',
};
const minimalScriptObj = await _createScript(minimalData);
await _verifyScript(minimalScriptObj, minimalData);
});
it('Create Script v3 - with defaults', async () => {
// Missing script field - should use default
const scriptWithoutScript = await request(context.app)
.post(`${API_PREFIX}/scripts`)
.set('xc-auth', context.token)
.send({
title: 'Test Without Script',
})
.expect(201);
expect(scriptWithoutScript.body).to.have.property('id');
expect(scriptWithoutScript.body.title).to.equal('Test Without Script');
expect(scriptWithoutScript.body).to.have.property('script');
// Script should be populated with default value
expect(scriptWithoutScript.body.script).to.be.a('string');
});
it('Read Script v3', async () => {
const scriptObj = await _createScript(scriptData);
// Get script
const getResponse = await request(context.app)
.get(`${API_PREFIX}/scripts/${scriptObj.id}`)
.set('xc-auth', context.token)
.expect(200);
const script = getResponse.body;
await _verifyScript(script, scriptData);
// Verify script content is included in get response
expect(script.script).to.equal(scriptData.script);
expect(script.config).to.deep.equal(scriptData.config);
});
it('Read Script v3 - not found', async () => {
// Non-existent script ID
const errNotFound = await request(context.app)
.get(`${API_PREFIX}/scripts/nonexistent_id`)
.set('xc-auth', context.token)
.expect(422);
expect(errNotFound.body).to.have.property('error');
expect(errNotFound.body).to.have.property('message');
});
it('Update Script v3', async () => {
const scriptObj = await _createScript(scriptData);
// Update script
const updateData: CreateScriptArgs = {
title: 'Updated Script',
description: 'Updated description',
script: 'console.log("Updated");',
config: { timeout: 60000 },
};
const updateResponse = await request(context.app)
.patch(`${API_PREFIX}/scripts/${scriptObj.id}`)
.set('xc-auth', context.token)
.send(updateData)
.expect(200);
const updatedScript = updateResponse.body;
await _verifyScript(updatedScript, updateData);
// Verify the script was actually updated by fetching it again
const getResponse = await request(context.app)
.get(`${API_PREFIX}/scripts/${scriptObj.id}`)
.set('xc-auth', context.token)
.expect(200);
expect(getResponse.body.title).to.equal('Updated Script');
expect(getResponse.body.script).to.equal('console.log("Updated");');
});
it('Update Script v3 - partial update', async () => {
const scriptObj = await _createScript(scriptData);
// Update only title
const partialUpdate = {
title: 'Partially Updated',
description: scriptData.description,
script: scriptData.script,
};
const updateResponse = await request(context.app)
.patch(`${API_PREFIX}/scripts/${scriptObj.id}`)
.set('xc-auth', context.token)
.send(partialUpdate)
.expect(200);
expect(updateResponse.body.title).to.equal('Partially Updated');
expect(updateResponse.body.script).to.equal(scriptData.script);
});
it('Update Script v3 - not found', async () => {
// Update non-existent script
const errNotFound = await request(context.app)
.patch(`${API_PREFIX}/scripts/nonexistent_id`)
.set('xc-auth', context.token)
.send({
title: 'Updated',
script: 'console.log("test");',
})
.expect(422);
expect(errNotFound.body).to.have.property('error');
});
it('Delete Script v3', async () => {
const scriptObj = await _createScript(scriptData);
// Delete script
await request(context.app)
.delete(`${API_PREFIX}/scripts/${scriptObj.id}`)
.set('xc-auth', context.token)
.expect(200);
// Verify script is deleted by trying to get it
await request(context.app)
.get(`${API_PREFIX}/scripts/${scriptObj.id}`)
.set('xc-auth', context.token)
.expect(422);
});
it('Delete Script v3 - not found', async () => {
// Delete non-existent script
const errNotFound = await request(context.app)
.delete(`${API_PREFIX}/scripts/nonexistent_id`)
.set('xc-auth', context.token)
.expect(422);
expect(errNotFound.body).to.have.property('error');
});
it('Multiple operations v3', async () => {
// Create
await _createScript({
title: 'Script 1',
script: 'console.log("1");',
});
const script2 = await _createScript({
title: 'Script 2',
script: 'console.log("2");',
});
const script3 = await _createScript({
title: 'Script 3',
script: 'console.log("3");',
});
// List - should have 3 scripts
let listResponse = await request(context.app)
.get(`${API_PREFIX}/scripts`)
.set('xc-auth', context.token)
.expect(200);
expect(listResponse.body.list).to.have.lengthOf(3);
// Update one
await request(context.app)
.patch(`${API_PREFIX}/scripts/${script2.id}`)
.set('xc-auth', context.token)
.send({
title: 'Script 2 Updated',
script: 'console.log("2 updated");',
})
.expect(200);
// Delete one
await request(context.app)
.delete(`${API_PREFIX}/scripts/${script3.id}`)
.set('xc-auth', context.token)
.expect(200);
// List - should have 2 scripts now
listResponse = await request(context.app)
.get(`${API_PREFIX}/scripts`)
.set('xc-auth', context.token)
.expect(200);
expect(listResponse.body.list).to.have.lengthOf(2);
// Verify the updated script
const updatedScript = listResponse.body.list.find(
(s) => s.id === script2.id,
);
expect(updatedScript.title).to.equal('Script 2 Updated');
});
it('Forbidden due to plan not sufficient', async () => {
featureMock = await overrideFeature({
workspace_id: context.fk_workspace_id,
feature: `${PlanFeatureTypes.FEATURE_API_SCRIPT_MANAGEMENT}`,
allowed: false,
});
// Try to list scripts
const listScripts = await request(context.app)
.get(`${API_PREFIX}/scripts`)
.set('xc-auth', context.token)
.expect(403);
// Validation
const error = listScripts.body;
expect(error).to.be.an('object');
expect(error).to.have.property('error', 'ERR_FORBIDDEN');
expect(error).to.have.property('message').that.includes('not sufficient');
});
});
}