@maxisoft/instantcms-mcp 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/__tests__/artifact-tool.test.js +196 -0
- package/dist/__tests__/db-tool.test.js +152 -0
- package/dist/__tests__/define-tool.test.js +106 -0
- package/dist/__tests__/find-tool.test.js +185 -0
- package/dist/__tests__/get-component-api.test.js +144 -0
- package/dist/__tests__/hardening.test.js +73 -0
- package/dist/__tests__/hooks-tool.test.js +173 -0
- package/dist/__tests__/mcp-integration-extra.test.js +166 -0
- package/dist/__tests__/mcp-integration.test.js +53 -0
- package/dist/__tests__/pagination.test.js +114 -0
- package/dist/__tests__/performance-baseline.test.js +56 -0
- package/dist/__tests__/project-patch.test.js +144 -0
- package/dist/__tests__/project-source.test.js +44 -0
- package/dist/__tests__/project-workflows-extra.test.js +165 -0
- package/dist/__tests__/project-workflows.test.js +51 -0
- package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
- package/dist/__tests__/serialization.test.js +170 -0
- package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
- package/dist/__tests__/template-development.test.js +78 -0
- package/dist/__tests__/template-productivity.test.js +87 -0
- package/dist/__tests__/tools.test.js +2143 -0
- package/dist/data/components.js +2818 -0
- package/dist/data/controllers-map.js +7571 -0
- package/dist/data/core-api.js +9356 -0
- package/dist/data/database-schema.js +4555 -0
- package/dist/data/events-map.js +1339 -0
- package/dist/data/fields-map.js +3692 -0
- package/dist/data/hooks.js +2257 -0
- package/dist/data/js-api.js +123 -0
- package/dist/data/libs-api.js +264 -0
- package/dist/data/routes-map.js +203 -0
- package/dist/data/schemas-validation.js +255 -0
- package/dist/data/schemas.js +1404 -0
- package/dist/data/traits-map.js +1004 -0
- package/dist/data/version-profiles.js +41 -0
- package/dist/data/widgets-map.js +131 -0
- package/dist/data/wysiwyg-map.js +486 -0
- package/dist/generated/components-source.js +14748 -0
- package/dist/generated/hooks-source.js +3399 -0
- package/dist/generated/knowledge-meta.js +101 -0
- package/dist/index.js +13 -0
- package/dist/registry/database-tools.js +112 -0
- package/dist/registry/extension-tools.js +473 -0
- package/dist/registry/generator-tools.js +506 -0
- package/dist/registry/knowledge-tools.js +441 -0
- package/dist/registry/language-tools.js +99 -0
- package/dist/registry/meta-tools.js +152 -0
- package/dist/registry/project-tools.js +48 -0
- package/dist/registry/resources.js +98 -0
- package/dist/registry/source-tools.js +211 -0
- package/dist/registry/template-development-tools.js +79 -0
- package/dist/server.js +32 -0
- package/dist/tools/addon-tool.js +1437 -0
- package/dist/tools/admin-partial-tool.js +498 -0
- package/dist/tools/api-tool.js +294 -0
- package/dist/tools/artifact-tool.js +95 -0
- package/dist/tools/cache-tool.js +360 -0
- package/dist/tools/component-tool.js +415 -0
- package/dist/tools/controllers-tool.js +125 -0
- package/dist/tools/cron-tool.js +197 -0
- package/dist/tools/crud-tool.js +659 -0
- package/dist/tools/db-tool.js +198 -0
- package/dist/tools/email-tool.js +222 -0
- package/dist/tools/external-api-tool.js +596 -0
- package/dist/tools/filter-tool.js +341 -0
- package/dist/tools/form-tool.js +275 -0
- package/dist/tools/grid-tool.js +189 -0
- package/dist/tools/hooks-tool.js +104 -0
- package/dist/tools/import-export-tool.js +548 -0
- package/dist/tools/lang-tool.js +251 -0
- package/dist/tools/layout-override-tool.js +125 -0
- package/dist/tools/layout-tool.js +548 -0
- package/dist/tools/maria-tool.js +127 -0
- package/dist/tools/mariadb.js +201 -0
- package/dist/tools/migration-tool.js +329 -0
- package/dist/tools/oauth-tool.js +520 -0
- package/dist/tools/parser/components-parser.js +76 -0
- package/dist/tools/parser/controllers-parser.js +313 -0
- package/dist/tools/parser/core-parser.js +294 -0
- package/dist/tools/parser/coverage-generator.js +424 -0
- package/dist/tools/parser/events-parser.js +127 -0
- package/dist/tools/parser/fields-parser.js +382 -0
- package/dist/tools/parser/hooks-parser.js +106 -0
- package/dist/tools/parser/sql-parser.js +197 -0
- package/dist/tools/parser/traits-parser.js +161 -0
- package/dist/tools/parser/widgets-parser.js +150 -0
- package/dist/tools/permission-tool.js +348 -0
- package/dist/tools/project-patch-tool.js +73 -0
- package/dist/tools/project-source-tool.js +188 -0
- package/dist/tools/project-workflow-tool.js +186 -0
- package/dist/tools/requirement-tool.js +212 -0
- package/dist/tools/scaffold-tool.js +815 -0
- package/dist/tools/seo-tool.js +412 -0
- package/dist/tools/source-tool.js +155 -0
- package/dist/tools/template-development-tool.js +271 -0
- package/dist/tools/template-overrides-tool.js +294 -0
- package/dist/tools/template-productivity-tool.js +319 -0
- package/dist/tools/template-tool.js +665 -0
- package/dist/tools/test-tool.js +183 -0
- package/dist/tools/webhook-tool.js +427 -0
- package/dist/tools/widget-tool.js +331 -0
- package/dist/tools/wysiwyg-tool.js +137 -0
- package/dist/types/scaffold.js +68 -0
- package/dist/utils/define-tool.js +37 -0
- package/dist/utils/find-tool.js +97 -0
- package/dist/utils/mcp-result.js +18 -0
- package/dist/utils/pagination.js +27 -0
- package/dist/utils/serialization.js +27 -0
- package/package.json +94 -0
|
@@ -0,0 +1,2143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const hooks_tool_1 = require("../tools/hooks-tool");
|
|
4
|
+
const addon_tool_1 = require("../tools/addon-tool");
|
|
5
|
+
const scaffold_tool_1 = require("../tools/scaffold-tool");
|
|
6
|
+
const crud_tool_1 = require("../tools/crud-tool");
|
|
7
|
+
const form_tool_1 = require("../tools/form-tool");
|
|
8
|
+
const grid_tool_1 = require("../tools/grid-tool");
|
|
9
|
+
const api_tool_1 = require("../tools/api-tool");
|
|
10
|
+
const test_tool_1 = require("../tools/test-tool");
|
|
11
|
+
const email_tool_1 = require("../tools/email-tool");
|
|
12
|
+
const layout_override_tool_1 = require("../tools/layout-override-tool");
|
|
13
|
+
const admin_partial_tool_1 = require("../tools/admin-partial-tool");
|
|
14
|
+
const template_overrides_tool_1 = require("../tools/template-overrides-tool");
|
|
15
|
+
const cron_tool_1 = require("../tools/cron-tool");
|
|
16
|
+
const permission_tool_1 = require("../tools/permission-tool");
|
|
17
|
+
const filter_tool_1 = require("../tools/filter-tool");
|
|
18
|
+
const seo_tool_1 = require("../tools/seo-tool");
|
|
19
|
+
const import_export_tool_1 = require("../tools/import-export-tool");
|
|
20
|
+
const cache_tool_1 = require("../tools/cache-tool");
|
|
21
|
+
const webhook_tool_1 = require("../tools/webhook-tool");
|
|
22
|
+
const external_api_tool_1 = require("../tools/external-api-tool");
|
|
23
|
+
const oauth_tool_1 = require("../tools/oauth-tool");
|
|
24
|
+
const component_tool_1 = require("../tools/component-tool");
|
|
25
|
+
const widget_tool_1 = require("../tools/widget-tool");
|
|
26
|
+
const template_tool_1 = require("../tools/template-tool");
|
|
27
|
+
const layout_tool_1 = require("../tools/layout-tool");
|
|
28
|
+
const db_tool_1 = require("../tools/db-tool");
|
|
29
|
+
const controllers_tool_1 = require("../tools/controllers-tool");
|
|
30
|
+
const source_tool_1 = require("../tools/source-tool");
|
|
31
|
+
const migration_tool_1 = require("../tools/migration-tool");
|
|
32
|
+
const requirement_tool_1 = require("../tools/requirement-tool");
|
|
33
|
+
describe('Hooks Tool', () => {
|
|
34
|
+
test('listHooks returns array', () => {
|
|
35
|
+
const result = (0, hooks_tool_1.listHooks)();
|
|
36
|
+
expect(result).toHaveProperty('total');
|
|
37
|
+
expect(result).toHaveProperty('hooks');
|
|
38
|
+
expect(Array.isArray(result.hooks)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
test('listHooks with category filter', () => {
|
|
41
|
+
const result = (0, hooks_tool_1.listHooks)('content');
|
|
42
|
+
expect(result.total).toBeGreaterThan(0);
|
|
43
|
+
result.hooks.forEach((hook) => {
|
|
44
|
+
expect(hook.category).toBe('content');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
test('getHookDetails returns hook details', () => {
|
|
48
|
+
const result = (0, hooks_tool_1.getHookDetails)('content_after_add_approve');
|
|
49
|
+
expect(result).toHaveProperty('name');
|
|
50
|
+
expect(result).toHaveProperty('description');
|
|
51
|
+
expect(result).toHaveProperty('example');
|
|
52
|
+
});
|
|
53
|
+
test('getHookDetails returns error for unknown hook', () => {
|
|
54
|
+
const result = (0, hooks_tool_1.getHookDetails)('unknown_hook_xyz');
|
|
55
|
+
expect(result).toHaveProperty('error');
|
|
56
|
+
});
|
|
57
|
+
test('searchHooks returns matching hooks', () => {
|
|
58
|
+
const result = (0, hooks_tool_1.searchHooks)('user registered');
|
|
59
|
+
expect(result).toHaveProperty('results');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('Addon Tool', () => {
|
|
63
|
+
test('getAddonStructure returns structure', () => {
|
|
64
|
+
const result = (0, addon_tool_1.getAddonStructure)('basic');
|
|
65
|
+
expect(result).toHaveProperty('type', 'basic');
|
|
66
|
+
expect(result).toHaveProperty('files');
|
|
67
|
+
expect(Array.isArray(result.files)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
test('getAddonStructure returns error for unknown type', () => {
|
|
70
|
+
const result = (0, addon_tool_1.getAddonStructure)('unknown');
|
|
71
|
+
expect(result).toHaveProperty('error');
|
|
72
|
+
});
|
|
73
|
+
test('getComponentApi returns component info', () => {
|
|
74
|
+
const result = (0, addon_tool_1.getComponentApi)('cmsModel');
|
|
75
|
+
expect(result).toHaveProperty('name');
|
|
76
|
+
expect(result).toHaveProperty('methods');
|
|
77
|
+
expect(Array.isArray(result.methods)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
test('getComponentApi returns error for unknown component', () => {
|
|
80
|
+
const result = (0, addon_tool_1.getComponentApi)('unknownComponent');
|
|
81
|
+
expect(result).toHaveProperty('error');
|
|
82
|
+
});
|
|
83
|
+
test('listComponents returns array', () => {
|
|
84
|
+
const result = (0, addon_tool_1.listComponents)();
|
|
85
|
+
expect(result).toHaveProperty('total');
|
|
86
|
+
expect(result.total).toBeGreaterThan(0);
|
|
87
|
+
expect(result).toHaveProperty('components');
|
|
88
|
+
});
|
|
89
|
+
test('validateAddon detects missing manifest.xml', () => {
|
|
90
|
+
const result = (0, addon_tool_1.validateAddon)({ 'frontend.php': 'test' });
|
|
91
|
+
expect(result.is_valid).toBe(false);
|
|
92
|
+
expect(result.errors).toContain('Отсутствует обязательный файл: manifest.xml');
|
|
93
|
+
});
|
|
94
|
+
test('validateAddon passes valid addon', () => {
|
|
95
|
+
const result = (0, addon_tool_1.validateAddon)({
|
|
96
|
+
'manifest.xml': '<addon><name>test</name><title>Test</title><version>1.0</version></addon>',
|
|
97
|
+
'install.php': 'class installerTest extends cmsInstaller { public function install() {} }',
|
|
98
|
+
'uninstall.php': 'class uninstallerTest extends cmsInstaller { public function uninstall() {} }',
|
|
99
|
+
'frontend.php': 'class test extends cmsFrontend { public function actionIndex() {} }',
|
|
100
|
+
});
|
|
101
|
+
expect(result.is_valid).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
test('getFieldTypes returns all types', () => {
|
|
104
|
+
const result = (0, addon_tool_1.getFieldTypes)();
|
|
105
|
+
expect(result).toHaveProperty('total');
|
|
106
|
+
expect(result.total).toBeGreaterThan(0);
|
|
107
|
+
expect(result).toHaveProperty('field_types');
|
|
108
|
+
});
|
|
109
|
+
test('getFieldTypes returns specific type', () => {
|
|
110
|
+
const result = (0, addon_tool_1.getFieldTypes)('fieldString');
|
|
111
|
+
expect(result).toHaveProperty('name', 'fieldString');
|
|
112
|
+
});
|
|
113
|
+
test('getCodeExample returns examples', () => {
|
|
114
|
+
const result = (0, addon_tool_1.getCodeExample)('список');
|
|
115
|
+
expect(result).toHaveProperty('found');
|
|
116
|
+
expect(result.found).toBeGreaterThan(0);
|
|
117
|
+
expect(result).toHaveProperty('examples');
|
|
118
|
+
});
|
|
119
|
+
test('getCodeExample returns tip for unknown task', () => {
|
|
120
|
+
const result = (0, addon_tool_1.getCodeExample)('xyz_unknown_task_123');
|
|
121
|
+
expect(result).toHaveProperty('available_topics');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('Scaffold Tool', () => {
|
|
125
|
+
test('scaffoldAddon generates files', () => {
|
|
126
|
+
const result = (0, scaffold_tool_1.scaffoldAddon)({ name: 'test_addon', title: 'Test Addon', type: 'basic' });
|
|
127
|
+
expect(result).toHaveProperty('files');
|
|
128
|
+
expect(Object.keys(result.files).length).toBeGreaterThan(0);
|
|
129
|
+
});
|
|
130
|
+
test('scaffoldAddon with_hooks generates hooks', () => {
|
|
131
|
+
const result = (0, scaffold_tool_1.scaffoldAddon)({
|
|
132
|
+
name: 'test_addon',
|
|
133
|
+
title: 'Test',
|
|
134
|
+
type: 'with_hooks',
|
|
135
|
+
hooks: ['content_after_add_approve'],
|
|
136
|
+
});
|
|
137
|
+
const hookContent = JSON.stringify(result.files);
|
|
138
|
+
expect(hookContent).toContain('content_after_add_approve');
|
|
139
|
+
});
|
|
140
|
+
test('scaffoldTemplate generates template files', () => {
|
|
141
|
+
const result = (0, scaffold_tool_1.scaffoldTemplate)({ name: 'test_theme', title: 'Test Theme' });
|
|
142
|
+
expect(result).toHaveProperty('files');
|
|
143
|
+
expect(Object.keys(result.files).length).toBeGreaterThan(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('CRUD Tool', () => {
|
|
147
|
+
test('scaffoldCrud generates basic CRUD files', () => {
|
|
148
|
+
const result = (0, crud_tool_1.scaffoldCrud)({
|
|
149
|
+
addon_name: 'test_crud',
|
|
150
|
+
fields: [
|
|
151
|
+
{ name: 'title', type: 'varchar', title: 'Заголовок' },
|
|
152
|
+
{ name: 'description', type: 'text', title: 'Описание' },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
expect(result).toHaveProperty('addon_name', 'test_crud');
|
|
156
|
+
expect(result).toHaveProperty('files');
|
|
157
|
+
expect('package/system/controllers/test_crud/model.php' in result.files).toBe(true);
|
|
158
|
+
expect('package/system/controllers/test_crud/frontend.php' in result.files).toBe(true);
|
|
159
|
+
expect('package/system/controllers/test_crud/actions/index.php' in result.files).toBe(true);
|
|
160
|
+
expect('package/system/controllers/test_crud/actions/view.php' in result.files).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
test('scaffoldCrud generates backend files', () => {
|
|
163
|
+
const result = (0, crud_tool_1.scaffoldCrud)({
|
|
164
|
+
addon_name: 'test_crud',
|
|
165
|
+
fields: [{ name: 'title', type: 'varchar', title: 'Заголовок' }],
|
|
166
|
+
});
|
|
167
|
+
expect('package/system/controllers/test_crud/backend.php' in result.files).toBe(true);
|
|
168
|
+
expect('package/system/controllers/test_crud/backend/actions/index.php' in result.files).toBe(true);
|
|
169
|
+
expect('package/system/controllers/test_crud/backend/actions/items.php' in result.files).toBe(true);
|
|
170
|
+
expect('package/system/controllers/test_crud/backend/grids/grid_items.php' in result.files).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
test('scaffoldCrud generates form files', () => {
|
|
173
|
+
const result = (0, crud_tool_1.scaffoldCrud)({
|
|
174
|
+
addon_name: 'test_crud',
|
|
175
|
+
fields: [
|
|
176
|
+
{ name: 'title', type: 'varchar', title: 'Заголовок' },
|
|
177
|
+
{ name: 'content', type: 'html', title: 'Контент' },
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
const formContent = result.files['package/system/controllers/test_crud/backend/forms/form_item.php'];
|
|
181
|
+
expect(formContent).toContain('fieldString');
|
|
182
|
+
expect(formContent).toContain('fieldHtml');
|
|
183
|
+
});
|
|
184
|
+
test('scaffoldCrud generates language file', () => {
|
|
185
|
+
const result = (0, crud_tool_1.scaffoldCrud)({
|
|
186
|
+
addon_name: 'test_crud',
|
|
187
|
+
fields: [{ name: 'title', type: 'varchar', title: 'Заголовок' }],
|
|
188
|
+
});
|
|
189
|
+
const langContent = result.files['package/system/languages/ru/controllers/test_crud/test_crud.php'];
|
|
190
|
+
expect(langContent).toContain('LANG_TEST_CRUD_TITLE');
|
|
191
|
+
});
|
|
192
|
+
test('scaffoldCrud with category option', () => {
|
|
193
|
+
const result = (0, crud_tool_1.scaffoldCrud)({
|
|
194
|
+
addon_name: 'test_crud',
|
|
195
|
+
fields: [{ name: 'title', type: 'varchar', title: 'Заголовок' }],
|
|
196
|
+
options: { use_category: true },
|
|
197
|
+
});
|
|
198
|
+
expect('package/system/controllers/test_crud/actions/category.php' in result.files).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
test('scaffoldCrud includes all CRUD actions', () => {
|
|
201
|
+
const result = (0, crud_tool_1.scaffoldCrud)({
|
|
202
|
+
addon_name: 'my_crud',
|
|
203
|
+
fields: [{ name: 'title', type: 'varchar', title: 'Title' }],
|
|
204
|
+
});
|
|
205
|
+
const files = result.files;
|
|
206
|
+
expect('package/system/controllers/my_crud/actions/add.php' in files).toBe(true);
|
|
207
|
+
expect('package/system/controllers/my_crud/actions/edit.php' in files).toBe(true);
|
|
208
|
+
expect('package/system/controllers/my_crud/actions/delete.php' in files).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe('Form Tool', () => {
|
|
212
|
+
test('scaffoldForm generates basic form', () => {
|
|
213
|
+
const result = (0, form_tool_1.scaffoldForm)({
|
|
214
|
+
addon_name: 'test_form',
|
|
215
|
+
form_name: 'item',
|
|
216
|
+
fields: [
|
|
217
|
+
{ name: 'title', type: 'varchar', title: 'Заголовок' },
|
|
218
|
+
{ name: 'content', type: 'text', title: 'Контент' },
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
expect(result).toHaveProperty('addon_name', 'test_form');
|
|
222
|
+
expect(result).toHaveProperty('form_name', 'item');
|
|
223
|
+
expect(result).toHaveProperty('form_class', 'formTestFormItem');
|
|
224
|
+
expect('package/system/controllers/test_form/backend/forms/form_item.php' in result.files).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
test('scaffoldForm generates correct field classes', () => {
|
|
227
|
+
const result = (0, form_tool_1.scaffoldForm)({
|
|
228
|
+
addon_name: 'test_form',
|
|
229
|
+
form_name: 'item',
|
|
230
|
+
fields: [
|
|
231
|
+
{ name: 'title', type: 'varchar', title: 'Title' },
|
|
232
|
+
{ name: 'body', type: 'html', title: 'Body' },
|
|
233
|
+
{ name: 'price', type: 'decimal', title: 'Price' },
|
|
234
|
+
{ name: 'is_active', type: 'checkbox', title: 'Active' },
|
|
235
|
+
{ name: 'published_at', type: 'date', title: 'Date' },
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
const formContent = result.files['package/system/controllers/test_form/backend/forms/form_item.php'];
|
|
239
|
+
expect(formContent).toContain('fieldString');
|
|
240
|
+
expect(formContent).toContain('fieldHtml');
|
|
241
|
+
expect(formContent).toContain('fieldNumber');
|
|
242
|
+
expect(formContent).toContain('fieldCheckbox');
|
|
243
|
+
expect(formContent).toContain('fieldDate');
|
|
244
|
+
});
|
|
245
|
+
test('scaffoldForm with tabs option', () => {
|
|
246
|
+
const result = (0, form_tool_1.scaffoldForm)({
|
|
247
|
+
addon_name: 'test_form',
|
|
248
|
+
form_name: 'item',
|
|
249
|
+
fields: [
|
|
250
|
+
{ name: 'title', type: 'varchar', title: 'Title' },
|
|
251
|
+
{ name: 'content', type: 'text', title: 'Content' },
|
|
252
|
+
],
|
|
253
|
+
options: { use_tabs: true },
|
|
254
|
+
});
|
|
255
|
+
const formContent = result.files['package/system/controllers/test_form/backend/forms/form_item.php'];
|
|
256
|
+
expect(formContent).toContain("'basic'");
|
|
257
|
+
});
|
|
258
|
+
test('scaffoldForm with separate save', () => {
|
|
259
|
+
const result = (0, form_tool_1.scaffoldForm)({
|
|
260
|
+
addon_name: 'test_form',
|
|
261
|
+
form_name: 'item',
|
|
262
|
+
fields: [{ name: 'title', type: 'varchar', title: 'Title' }],
|
|
263
|
+
options: { use_separate_save: true },
|
|
264
|
+
});
|
|
265
|
+
expect('package/system/controllers/test_form/backend/forms/form_item_save.php' in result.files).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
test('scaffoldForm returns correct field info', () => {
|
|
268
|
+
const result = (0, form_tool_1.scaffoldForm)({
|
|
269
|
+
addon_name: 'test_form',
|
|
270
|
+
form_name: 'item',
|
|
271
|
+
fields: [
|
|
272
|
+
{ name: 'title', type: 'varchar' },
|
|
273
|
+
{ name: 'image', type: 'image' },
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
expect(result.form_fields).toHaveLength(2);
|
|
277
|
+
expect(result.form_fields[0]).toEqual({ name: 'title', type: 'varchar', class: 'fieldString' });
|
|
278
|
+
expect(result.form_fields[1]).toEqual({ name: 'image', type: 'image', class: 'fieldImage' });
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
describe('Grid Tool', () => {
|
|
282
|
+
test('scaffoldGrid generates grid file', () => {
|
|
283
|
+
const result = (0, grid_tool_1.scaffoldGrid)({
|
|
284
|
+
addon_name: 'test_grid',
|
|
285
|
+
grid_name: 'items',
|
|
286
|
+
columns: [
|
|
287
|
+
{ name: 'id', title: 'ID', width: 60 },
|
|
288
|
+
{
|
|
289
|
+
name: 'title',
|
|
290
|
+
title: 'Title',
|
|
291
|
+
filter: 'like',
|
|
292
|
+
href: "href_to($controller->root_url, 'items', ['edit', '{id}'])",
|
|
293
|
+
},
|
|
294
|
+
{ name: 'date_pub', title: 'Date', width: 150, filter: 'date' },
|
|
295
|
+
{ name: 'is_pub', title: 'Published', width: 80, flag: true },
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
expect(result).toHaveProperty('addon_name', 'test_grid');
|
|
299
|
+
expect(result).toHaveProperty('grid_name', 'items');
|
|
300
|
+
expect(result).toHaveProperty('function_name', 'grid_items');
|
|
301
|
+
expect('package/system/controllers/test_grid/backend/grids/grid_items.php' in result.files).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
test('scaffoldGrid with default columns', () => {
|
|
304
|
+
const result = (0, grid_tool_1.scaffoldGrid)({
|
|
305
|
+
addon_name: 'test_grid',
|
|
306
|
+
grid_name: 'items',
|
|
307
|
+
columns: [{ name: 'title', title: 'Title' }],
|
|
308
|
+
});
|
|
309
|
+
const gridContent = result.files['package/system/controllers/test_grid/backend/grids/grid_items.php'];
|
|
310
|
+
expect(gridContent).toContain('function grid_items');
|
|
311
|
+
expect(gridContent).toContain("'is_sortable'");
|
|
312
|
+
expect(gridContent).toContain("'is_filter'");
|
|
313
|
+
});
|
|
314
|
+
test('scaffoldGrid with draggable option', () => {
|
|
315
|
+
const result = (0, grid_tool_1.scaffoldGrid)({
|
|
316
|
+
addon_name: 'test_grid',
|
|
317
|
+
grid_name: 'items',
|
|
318
|
+
columns: [{ name: 'title', title: 'Title' }],
|
|
319
|
+
options: { is_draggable: true },
|
|
320
|
+
});
|
|
321
|
+
const gridContent = result.files['package/system/controllers/test_grid/backend/grids/grid_items.php'];
|
|
322
|
+
expect(gridContent).toContain("'is_draggable' => true");
|
|
323
|
+
});
|
|
324
|
+
test('scaffoldGrid with custom actions', () => {
|
|
325
|
+
const result = (0, grid_tool_1.scaffoldGrid)({
|
|
326
|
+
addon_name: 'test_grid',
|
|
327
|
+
grid_name: 'items',
|
|
328
|
+
columns: [{ name: 'title', title: 'Title' }],
|
|
329
|
+
actions: [
|
|
330
|
+
{ title: 'VIEW', href: "href_to('test_grid', '{id}')", icon: 'eye' },
|
|
331
|
+
{
|
|
332
|
+
title: 'EDIT',
|
|
333
|
+
href: "href_to($controller->root_url, 'items', ['edit', '{id}'])",
|
|
334
|
+
icon: 'pen',
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
const gridContent = result.files['package/system/controllers/test_grid/backend/grids/grid_items.php'];
|
|
339
|
+
expect(gridContent).toContain('LANG_VIEW');
|
|
340
|
+
expect(gridContent).toContain('LANG_EDIT');
|
|
341
|
+
});
|
|
342
|
+
test('scaffoldGrid returns correct structure', () => {
|
|
343
|
+
const result = (0, grid_tool_1.scaffoldGrid)({
|
|
344
|
+
addon_name: 'test_grid',
|
|
345
|
+
grid_name: 'items',
|
|
346
|
+
columns: [
|
|
347
|
+
{ name: 'title', title: 'Title' },
|
|
348
|
+
{ name: 'is_pub', title: 'Published', flag: true },
|
|
349
|
+
],
|
|
350
|
+
options: { is_sortable: false, is_filter: false },
|
|
351
|
+
});
|
|
352
|
+
expect(result.columns_count).toBe(2);
|
|
353
|
+
expect(result.options.is_sortable).toBe(false);
|
|
354
|
+
expect(result.options.is_filter).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
describe('API Tool', () => {
|
|
358
|
+
test('scaffoldApi generates API controller', () => {
|
|
359
|
+
const result = (0, api_tool_1.scaffoldApi)({
|
|
360
|
+
addon_name: 'test_api',
|
|
361
|
+
version: 'v1',
|
|
362
|
+
endpoints: [
|
|
363
|
+
{ name: 'list', method: 'GET', path: '/list', description: 'Get list' },
|
|
364
|
+
{ name: 'get', method: 'GET', path: '/{id}', description: 'Get one' },
|
|
365
|
+
{ name: 'create', method: 'POST', path: '/', description: 'Create' },
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
expect(result).toHaveProperty('addon_name', 'test_api');
|
|
369
|
+
expect(result).toHaveProperty('api_version', 'v1');
|
|
370
|
+
expect(result).toHaveProperty('endpoints_count', 3);
|
|
371
|
+
expect('package/system/controllers/test_api/api/v1/index.php' in result.files).toBe(true);
|
|
372
|
+
});
|
|
373
|
+
test('scaffoldApi with swagger', () => {
|
|
374
|
+
const result = (0, api_tool_1.scaffoldApi)({
|
|
375
|
+
addon_name: 'test_api',
|
|
376
|
+
version: 'v1',
|
|
377
|
+
endpoints: [{ name: 'list', method: 'GET', path: '/list' }],
|
|
378
|
+
options: { use_swagger: true },
|
|
379
|
+
});
|
|
380
|
+
expect('package/docs/openapi.json' in result.files).toBe(true);
|
|
381
|
+
});
|
|
382
|
+
test('scaffoldApi returns correct endpoint info', () => {
|
|
383
|
+
const result = (0, api_tool_1.scaffoldApi)({
|
|
384
|
+
addon_name: 'test_api',
|
|
385
|
+
version: 'v1',
|
|
386
|
+
endpoints: [{ name: 'list', method: 'GET', path: '/list', auth_required: false }],
|
|
387
|
+
});
|
|
388
|
+
expect(result.endpoints[0]).toEqual({
|
|
389
|
+
method: 'GET',
|
|
390
|
+
path: '/api/v1/test_api/list',
|
|
391
|
+
name: 'list',
|
|
392
|
+
auth_required: false,
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
test('scaffoldApi generates GET handler', () => {
|
|
396
|
+
const result = (0, api_tool_1.scaffoldApi)({
|
|
397
|
+
addon_name: 'test_api',
|
|
398
|
+
endpoints: [{ name: 'list', method: 'GET', path: '/list' }],
|
|
399
|
+
});
|
|
400
|
+
const apiContent = result.files['package/system/controllers/test_api/api/v1/index.php'];
|
|
401
|
+
expect(apiContent).toContain('function run()');
|
|
402
|
+
expect(apiContent).toContain('checkAuth');
|
|
403
|
+
});
|
|
404
|
+
test('scaffoldApi generates POST handler', () => {
|
|
405
|
+
const result = (0, api_tool_1.scaffoldApi)({
|
|
406
|
+
addon_name: 'test_api',
|
|
407
|
+
endpoints: [{ name: 'create', method: 'POST', path: '/' }],
|
|
408
|
+
});
|
|
409
|
+
const apiContent = result.files['package/system/controllers/test_api/api/v1/index.php'];
|
|
410
|
+
expect(apiContent).toContain('addTest_api');
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
describe('Test Tool', () => {
|
|
414
|
+
test('scaffoldTest generates phpunit test', () => {
|
|
415
|
+
const result = (0, test_tool_1.scaffoldTest)({
|
|
416
|
+
addon_name: 'test_addon',
|
|
417
|
+
class_name: 'TestAddon',
|
|
418
|
+
class_type: 'model',
|
|
419
|
+
methods: ['getItem', 'getList', 'save'],
|
|
420
|
+
});
|
|
421
|
+
expect(result).toHaveProperty('addon_name', 'test_addon');
|
|
422
|
+
expect(result).toHaveProperty('test_framework', 'phpunit');
|
|
423
|
+
expect('tests/phpunit/TestAddonTest.php' in result.files).toBe(true);
|
|
424
|
+
expect('phpunit.xml' in result.files).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
test('scaffoldTest generates codeception test', () => {
|
|
427
|
+
const result = (0, test_tool_1.scaffoldTest)({
|
|
428
|
+
addon_name: 'test_addon',
|
|
429
|
+
class_name: 'TestAddon',
|
|
430
|
+
class_type: 'controller',
|
|
431
|
+
methods: ['run', 'actionIndex'],
|
|
432
|
+
options: { test_framework: 'codeception' },
|
|
433
|
+
});
|
|
434
|
+
expect(result).toHaveProperty('test_framework', 'codeception');
|
|
435
|
+
expect('tests/unit/test_addon/TestAddonTest.php' in result.files).toBe(true);
|
|
436
|
+
expect('tests/unit.suite.yml' in result.files).toBe(true);
|
|
437
|
+
});
|
|
438
|
+
test('scaffoldTest generates correct method tests', () => {
|
|
439
|
+
const result = (0, test_tool_1.scaffoldTest)({
|
|
440
|
+
addon_name: 'test_addon',
|
|
441
|
+
class_name: 'ModelTest',
|
|
442
|
+
class_type: 'model',
|
|
443
|
+
methods: ['getItem', 'save'],
|
|
444
|
+
});
|
|
445
|
+
const testContent = result.files['tests/phpunit/ModelTestTest.php'];
|
|
446
|
+
expect(testContent).toContain('testGetItem');
|
|
447
|
+
expect(testContent).toContain('testSave');
|
|
448
|
+
});
|
|
449
|
+
test('scaffoldTest returns correct structure', () => {
|
|
450
|
+
const result = (0, test_tool_1.scaffoldTest)({
|
|
451
|
+
addon_name: 'test_addon',
|
|
452
|
+
class_name: 'TestAddon',
|
|
453
|
+
class_type: 'model',
|
|
454
|
+
methods: ['method1', 'method2', 'method3'],
|
|
455
|
+
});
|
|
456
|
+
expect(result.methods_count).toBe(3);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
describe('Email Tool', () => {
|
|
460
|
+
test('scaffoldEmail generates email templates', () => {
|
|
461
|
+
const result = (0, email_tool_1.scaffoldEmail)({
|
|
462
|
+
addon_name: 'test_email',
|
|
463
|
+
templates: [
|
|
464
|
+
{ name: 'welcome', subject: 'Добро пожаловать!', body: 'Привет {user_name}!' },
|
|
465
|
+
{ name: 'notification', subject: 'Уведомление', body: 'Новое уведомление для {user_name}' },
|
|
466
|
+
],
|
|
467
|
+
});
|
|
468
|
+
expect(result).toHaveProperty('addon_name', 'test_email');
|
|
469
|
+
expect(result).toHaveProperty('templates_count', 2);
|
|
470
|
+
expect('package/system/languages/ru/controllers/test_email/test_email_welcome.email.php' in
|
|
471
|
+
result.files).toBe(true);
|
|
472
|
+
expect('package/system/languages/ru/controllers/test_email/test_email_notification.email.php' in
|
|
473
|
+
result.files).toBe(true);
|
|
474
|
+
});
|
|
475
|
+
test('scaffoldEmail generates email index', () => {
|
|
476
|
+
const result = (0, email_tool_1.scaffoldEmail)({
|
|
477
|
+
addon_name: 'test_email',
|
|
478
|
+
templates: [{ name: 'welcome', subject: 'Test', body: 'Body' }],
|
|
479
|
+
});
|
|
480
|
+
expect('package/system/languages/ru/controllers/test_email/test_email_emails.php' in result.files).toBe(true);
|
|
481
|
+
});
|
|
482
|
+
test('scaffoldEmail with HTML', () => {
|
|
483
|
+
const result = (0, email_tool_1.scaffoldEmail)({
|
|
484
|
+
addon_name: 'test_email',
|
|
485
|
+
templates: [{ name: 'welcome', subject: 'Test', body: 'Hello' }],
|
|
486
|
+
options: { use_html: true, base_template: 'default' },
|
|
487
|
+
});
|
|
488
|
+
const emailContent = result.files['package/system/languages/ru/controllers/test_email/test_email_welcome.email.php'];
|
|
489
|
+
expect(emailContent).toContain('HTML');
|
|
490
|
+
expect(emailContent).toContain('email-container');
|
|
491
|
+
});
|
|
492
|
+
test('scaffoldEmail returns correct template info', () => {
|
|
493
|
+
const result = (0, email_tool_1.scaffoldEmail)({
|
|
494
|
+
addon_name: 'test_email',
|
|
495
|
+
templates: [
|
|
496
|
+
{ name: 'test', subject: 'Test Subject', body: 'Body', variables: [{ name: 'user_name' }] },
|
|
497
|
+
],
|
|
498
|
+
});
|
|
499
|
+
expect(result.templates[0].name).toBe('test');
|
|
500
|
+
expect(result.templates[0].variables_count).toBe(1);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
describe('Layout Override Tool', () => {
|
|
504
|
+
test('scaffoldLayoutOverride generates override templates', () => {
|
|
505
|
+
const result = (0, layout_override_tool_1.scaffoldLayoutOverride)({
|
|
506
|
+
addon_name: 'test_override',
|
|
507
|
+
overrides: [
|
|
508
|
+
{ controller: 'content', template: 'modern', action: 'view' },
|
|
509
|
+
{ controller: 'users', template: 'modern' },
|
|
510
|
+
],
|
|
511
|
+
});
|
|
512
|
+
expect(result).toHaveProperty('addon_name', 'test_override');
|
|
513
|
+
expect(result).toHaveProperty('overrides_count', 2);
|
|
514
|
+
expect('templates/modern/controllers/content/view.tpl.php' in result.files).toBe(true);
|
|
515
|
+
expect('templates/modern/controllers/users/index.tpl.php' in result.files).toBe(true);
|
|
516
|
+
});
|
|
517
|
+
test('scaffoldLayoutOverride generates language file', () => {
|
|
518
|
+
const result = (0, layout_override_tool_1.scaffoldLayoutOverride)({
|
|
519
|
+
addon_name: 'test_override',
|
|
520
|
+
overrides: [{ controller: 'content', template: 'modern', action: 'view' }],
|
|
521
|
+
});
|
|
522
|
+
expect('package/system/languages/ru/controllers/test_override/test_override.php' in result.files).toBe(true);
|
|
523
|
+
});
|
|
524
|
+
test('scaffoldLayoutOverride returns correct override info', () => {
|
|
525
|
+
const result = (0, layout_override_tool_1.scaffoldLayoutOverride)({
|
|
526
|
+
addon_name: 'test_override',
|
|
527
|
+
overrides: [{ controller: 'content', template: 'modern', action: 'view' }],
|
|
528
|
+
});
|
|
529
|
+
expect(result.overrides[0].controller).toBe('content');
|
|
530
|
+
expect(result.overrides[0].template).toBe('modern');
|
|
531
|
+
expect(result.overrides[0].action).toBe('view');
|
|
532
|
+
expect(result.overrides[0].path).toBe('templates/modern/controllers/content/view.tpl.php');
|
|
533
|
+
});
|
|
534
|
+
test('scaffoldLayoutOverride with options', () => {
|
|
535
|
+
const result = (0, layout_override_tool_1.scaffoldLayoutOverride)({
|
|
536
|
+
addon_name: 'test_override',
|
|
537
|
+
overrides: [{ controller: 'content', template: 'modern' }],
|
|
538
|
+
options: { use_wrapper: true, add_breadcrumbs: true },
|
|
539
|
+
});
|
|
540
|
+
const content = result.files['templates/modern/controllers/content/index.tpl.php'];
|
|
541
|
+
expect(content).toContain('content-breadcrumbs');
|
|
542
|
+
expect(content).toContain('test_override-sidebar');
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe('Admin Partial Tool', () => {
|
|
546
|
+
test('scaffoldAdminPartial generates partials', () => {
|
|
547
|
+
const result = (0, admin_partial_tool_1.scaffoldAdminPartial)({
|
|
548
|
+
addon_name: 'test_partial',
|
|
549
|
+
partials: [
|
|
550
|
+
{ name: 'menu', type: 'sidebar' },
|
|
551
|
+
{ name: 'info', type: 'panel' },
|
|
552
|
+
],
|
|
553
|
+
});
|
|
554
|
+
expect(result).toHaveProperty('addon_name', 'test_partial');
|
|
555
|
+
expect(result).toHaveProperty('partials_count', 2);
|
|
556
|
+
expect('package/templates/admincoreui/partials/test_partial/sidebar_menu.php' in result.files).toBe(true);
|
|
557
|
+
expect('package/templates/admincoreui/partials/test_partial/panel_info.php' in result.files).toBe(true);
|
|
558
|
+
});
|
|
559
|
+
test('scaffoldAdminPartial generates header partial', () => {
|
|
560
|
+
const result = (0, admin_partial_tool_1.scaffoldAdminPartial)({
|
|
561
|
+
addon_name: 'test_partial',
|
|
562
|
+
partials: [{ name: 'main', type: 'header', items: ['dashboard', 'users'] }],
|
|
563
|
+
});
|
|
564
|
+
const content = result.files['package/templates/admincoreui/partials/test_partial/header_main.php'];
|
|
565
|
+
expect(content).toContain('navbar');
|
|
566
|
+
expect(content).toContain('icon-');
|
|
567
|
+
});
|
|
568
|
+
test('scaffoldAdminPartial generates modal partial', () => {
|
|
569
|
+
const result = (0, admin_partial_tool_1.scaffoldAdminPartial)({
|
|
570
|
+
addon_name: 'test_partial',
|
|
571
|
+
partials: [{ name: 'confirm', type: 'modal' }],
|
|
572
|
+
});
|
|
573
|
+
const content = result.files['package/templates/admincoreui/partials/test_partial/modal_confirm.php'];
|
|
574
|
+
expect(content).toContain('modal');
|
|
575
|
+
expect(content).toContain('modal-title');
|
|
576
|
+
});
|
|
577
|
+
test('scaffoldAdminPartial generates index file', () => {
|
|
578
|
+
const result = (0, admin_partial_tool_1.scaffoldAdminPartial)({
|
|
579
|
+
addon_name: 'test_partial',
|
|
580
|
+
partials: [{ name: 'menu', type: 'sidebar' }],
|
|
581
|
+
});
|
|
582
|
+
expect('package/templates/admincoreui/partials/test_partial/test_partial.php' in result.files).toBe(true);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
describe('Template Overrides Tool', () => {
|
|
586
|
+
test('listTemplateOverrides returns all overrides', () => {
|
|
587
|
+
const result = (0, template_overrides_tool_1.listTemplateOverrides)();
|
|
588
|
+
expect(result).toHaveProperty('total');
|
|
589
|
+
expect(result.total).toBeGreaterThan(0);
|
|
590
|
+
expect(result).toHaveProperty('overrides');
|
|
591
|
+
expect(Array.isArray(result.overrides)).toBe(true);
|
|
592
|
+
});
|
|
593
|
+
test('listTemplateOverrides filters by controller', () => {
|
|
594
|
+
const result = (0, template_overrides_tool_1.listTemplateOverrides)('content');
|
|
595
|
+
expect(result.controller).toBe('content');
|
|
596
|
+
result.overrides.forEach((o) => {
|
|
597
|
+
expect(o.controller.toLowerCase()).toBe('content');
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
test('listTemplateOverrides returns correct structure', () => {
|
|
601
|
+
const result = (0, template_overrides_tool_1.listTemplateOverrides)('users');
|
|
602
|
+
expect(result.overrides[0]).toHaveProperty('controller');
|
|
603
|
+
expect(result.overrides[0]).toHaveProperty('action');
|
|
604
|
+
expect(result.overrides[0]).toHaveProperty('path');
|
|
605
|
+
expect(result.overrides[0]).toHaveProperty('description');
|
|
606
|
+
});
|
|
607
|
+
test('getTemplateOverrideInfo returns override details', () => {
|
|
608
|
+
const result = (0, template_overrides_tool_1.getTemplateOverrideInfo)('content', 'view');
|
|
609
|
+
expect(result).toHaveProperty('controller', 'content');
|
|
610
|
+
expect(result).toHaveProperty('action', 'view');
|
|
611
|
+
expect(result).toHaveProperty('path');
|
|
612
|
+
expect(result).toHaveProperty('example');
|
|
613
|
+
});
|
|
614
|
+
test('getTemplateOverrideInfo returns error for unknown', () => {
|
|
615
|
+
const result = (0, template_overrides_tool_1.getTemplateOverrideInfo)('unknown_controller');
|
|
616
|
+
expect(result).toHaveProperty('error');
|
|
617
|
+
expect(result).toHaveProperty('available');
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
describe('Cron Tool', () => {
|
|
621
|
+
test('scaffoldCron generates cron files', () => {
|
|
622
|
+
const result = (0, cron_tool_1.scaffoldCron)({
|
|
623
|
+
addon_name: 'test_cron',
|
|
624
|
+
tasks: [
|
|
625
|
+
{
|
|
626
|
+
name: 'cleanup',
|
|
627
|
+
schedule: { minute: '0', hour: '*', day: '*', month: '*', day_of_week: '*' },
|
|
628
|
+
action: 'taskCleanup',
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: 'send_notifications',
|
|
632
|
+
schedule: { minute: '*/15' },
|
|
633
|
+
action: 'taskSendNotifications',
|
|
634
|
+
},
|
|
635
|
+
],
|
|
636
|
+
});
|
|
637
|
+
expect(result).toHaveProperty('addon_name', 'test_cron');
|
|
638
|
+
expect(result).toHaveProperty('tasks_count', 2);
|
|
639
|
+
expect('package/system/controllers/test_cron/cron.php' in result.files).toBe(true);
|
|
640
|
+
});
|
|
641
|
+
test('scaffoldCron generates manifest with tasks', () => {
|
|
642
|
+
const result = (0, cron_tool_1.scaffoldCron)({
|
|
643
|
+
addon_name: 'test_cron',
|
|
644
|
+
tasks: [{ name: 'daily', schedule: { minute: '0', hour: '0' }, action: 'taskDaily' }],
|
|
645
|
+
});
|
|
646
|
+
const manifest = result.files['package/system/controllers/test_cron/manifest.xml'];
|
|
647
|
+
expect(manifest).toContain('<task name="daily"');
|
|
648
|
+
expect(manifest).toContain('schedule="0 0 * * *"');
|
|
649
|
+
});
|
|
650
|
+
test('scaffoldCron generates language file', () => {
|
|
651
|
+
const result = (0, cron_tool_1.scaffoldCron)({
|
|
652
|
+
addon_name: 'test_cron',
|
|
653
|
+
tasks: [
|
|
654
|
+
{
|
|
655
|
+
name: 'cleanup',
|
|
656
|
+
schedule: { minute: '0' },
|
|
657
|
+
action: 'taskCleanup',
|
|
658
|
+
description: 'Clean old data',
|
|
659
|
+
},
|
|
660
|
+
],
|
|
661
|
+
});
|
|
662
|
+
expect('package/system/languages/ru/controllers/test_cron/test_cron.php' in result.files).toBe(true);
|
|
663
|
+
const lang = result.files['package/system/languages/ru/controllers/test_cron/test_cron.php'];
|
|
664
|
+
expect(lang).toContain('LANG_TEST_CRON_TASK_CLEANUP');
|
|
665
|
+
});
|
|
666
|
+
test('scaffoldCron returns correct task info', () => {
|
|
667
|
+
const result = (0, cron_tool_1.scaffoldCron)({
|
|
668
|
+
addon_name: 'test_cron',
|
|
669
|
+
tasks: [{ name: 'hourly', schedule: { minute: '0', hour: '*' }, action: 'taskHourly' }],
|
|
670
|
+
});
|
|
671
|
+
expect(result.tasks[0].name).toBe('hourly');
|
|
672
|
+
expect(result.tasks[0].schedule).toBe('0 * * * *');
|
|
673
|
+
});
|
|
674
|
+
test('scaffoldCron with lock and logging options', () => {
|
|
675
|
+
const result = (0, cron_tool_1.scaffoldCron)({
|
|
676
|
+
addon_name: 'test_cron',
|
|
677
|
+
tasks: [{ name: 'test', schedule: { minute: '*' }, action: 'taskTest' }],
|
|
678
|
+
options: { use_lock_file: true, log_execution: true },
|
|
679
|
+
});
|
|
680
|
+
const cron = result.files['package/system/controllers/test_cron/cron.php'];
|
|
681
|
+
expect(cron).toContain('is_locked');
|
|
682
|
+
expect(cron).toContain('cache/logs/test_cron.log');
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
describe('Permission Tool', () => {
|
|
686
|
+
test('scaffoldPermission generates permission files', () => {
|
|
687
|
+
const result = (0, permission_tool_1.scaffoldPermission)({
|
|
688
|
+
name: 'blog_post',
|
|
689
|
+
title: 'Записи блога',
|
|
690
|
+
});
|
|
691
|
+
expect(result).toHaveProperty('addon_name', 'blog_post');
|
|
692
|
+
expect(result).toHaveProperty('title', 'Записи блога');
|
|
693
|
+
expect(result).toHaveProperty('permissions_count', 4);
|
|
694
|
+
expect('blog_post/manifest.json' in result.files).toBe(true);
|
|
695
|
+
expect('system/config/permissions/blog_post.php' in result.files).toBe(true);
|
|
696
|
+
});
|
|
697
|
+
test('scaffoldPermission includes permission checker class', () => {
|
|
698
|
+
const result = (0, permission_tool_1.scaffoldPermission)({
|
|
699
|
+
name: 'article',
|
|
700
|
+
title: 'Статьи',
|
|
701
|
+
permissions: ['view', 'add', 'edit', 'delete', 'publish'],
|
|
702
|
+
});
|
|
703
|
+
const permFile = result.files['article/permissions.php'];
|
|
704
|
+
expect(permFile).toContain('ArticlePermissions');
|
|
705
|
+
expect(permFile).toContain('function can(');
|
|
706
|
+
expect(permFile).toContain("'view'");
|
|
707
|
+
expect(permFile).toContain("'publish'");
|
|
708
|
+
});
|
|
709
|
+
test('scaffoldPermission generates hook handlers', () => {
|
|
710
|
+
const result = (0, permission_tool_1.scaffoldPermission)({
|
|
711
|
+
name: 'news_item',
|
|
712
|
+
title: 'Новости',
|
|
713
|
+
});
|
|
714
|
+
const hookFile = result.files['system/hooks/news_item/permissions.hooks.php'];
|
|
715
|
+
expect(hookFile).toContain('onNewsItemPermissionsHook');
|
|
716
|
+
expect(hookFile).toContain('onContentBeforeSave');
|
|
717
|
+
expect(hookFile).toContain('onContentBeforeDelete');
|
|
718
|
+
});
|
|
719
|
+
test('scaffoldPermission generates admin backend', () => {
|
|
720
|
+
const result = (0, permission_tool_1.scaffoldPermission)({
|
|
721
|
+
name: 'test_perm',
|
|
722
|
+
title: 'Тест',
|
|
723
|
+
permissions: ['view', 'edit'],
|
|
724
|
+
});
|
|
725
|
+
const backendFile = result.files['test_perm/backend/permissions.php'];
|
|
726
|
+
expect(backendFile).toContain('backendTestPermPermissions');
|
|
727
|
+
expect(backendFile).toContain('actionIndex');
|
|
728
|
+
expect(backendFile).toContain('actionSave');
|
|
729
|
+
});
|
|
730
|
+
test('scaffoldPermission with roles option', () => {
|
|
731
|
+
const result = (0, permission_tool_1.scaffoldPermission)({
|
|
732
|
+
name: 'catalog',
|
|
733
|
+
title: 'Каталог',
|
|
734
|
+
permissions: ['view', 'add', 'edit', 'delete', 'admin'],
|
|
735
|
+
options: { withRoles: true, withOwnership: true, withCategories: true },
|
|
736
|
+
});
|
|
737
|
+
expect('catalog/roles.php' in result.files).toBe(true);
|
|
738
|
+
const rolesFile = result.files['catalog/roles.php'];
|
|
739
|
+
expect(rolesFile).toContain('admin');
|
|
740
|
+
expect(rolesFile).toContain('moderator');
|
|
741
|
+
expect(rolesFile).toContain('auth');
|
|
742
|
+
expect(rolesFile).toContain('all');
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
describe('Filter Tool', () => {
|
|
746
|
+
test('scaffoldFilter generates filter files', () => {
|
|
747
|
+
const result = (0, filter_tool_1.scaffoldFilter)({
|
|
748
|
+
addon_name: 'catalog',
|
|
749
|
+
fields: [
|
|
750
|
+
{ field: 'price', type: 'range', label: 'Цена' },
|
|
751
|
+
{ field: 'category_id', type: 'select', label: 'Категория' },
|
|
752
|
+
],
|
|
753
|
+
});
|
|
754
|
+
expect(result).toHaveProperty('addon_name', 'catalog');
|
|
755
|
+
expect(result).toHaveProperty('filters_count', 2);
|
|
756
|
+
expect('catalog/filters.php' in result.files).toBe(true);
|
|
757
|
+
expect('catalog/filter.form.php' in result.files).toBe(true);
|
|
758
|
+
});
|
|
759
|
+
test('scaffoldFilter generates filter class with conditions', () => {
|
|
760
|
+
const result = (0, filter_tool_1.scaffoldFilter)({
|
|
761
|
+
addon_name: 'products',
|
|
762
|
+
fields: [
|
|
763
|
+
{ field: 'name', type: 'text', label: 'Название' },
|
|
764
|
+
{ field: 'price', type: 'range', label: 'Цена' },
|
|
765
|
+
{ field: 'in_stock', type: 'checkbox', label: 'В наличии' },
|
|
766
|
+
],
|
|
767
|
+
});
|
|
768
|
+
const filterFile = result.files['products/filters.php'];
|
|
769
|
+
expect(filterFile).toContain('ProductsFilter');
|
|
770
|
+
expect(filterFile).toContain('function apply(');
|
|
771
|
+
expect(filterFile).toContain("'text'");
|
|
772
|
+
expect(filterFile).toContain("'range'");
|
|
773
|
+
expect(filterFile).toContain("'checkbox'");
|
|
774
|
+
});
|
|
775
|
+
test('scaffoldFilter generates form fields', () => {
|
|
776
|
+
const result = (0, filter_tool_1.scaffoldFilter)({
|
|
777
|
+
addon_name: 'test_filter',
|
|
778
|
+
fields: [
|
|
779
|
+
{ field: 'category', type: 'select', label: 'Категория' },
|
|
780
|
+
{ field: 'date_from', type: 'date', label: 'Дата от' },
|
|
781
|
+
],
|
|
782
|
+
});
|
|
783
|
+
const formFile = result.files['test_filter/filter.form.php'];
|
|
784
|
+
expect(formFile).toContain('fieldList');
|
|
785
|
+
expect(formFile).toContain('fieldDate');
|
|
786
|
+
});
|
|
787
|
+
test('scaffoldFilter generates hook handlers', () => {
|
|
788
|
+
const result = (0, filter_tool_1.scaffoldFilter)({
|
|
789
|
+
addon_name: 'news',
|
|
790
|
+
fields: [{ field: 'title', type: 'text', label: 'Заголовок' }],
|
|
791
|
+
});
|
|
792
|
+
const hookFile = result.files['system/hooks/news/filter.hooks.php'];
|
|
793
|
+
expect(hookFile).toContain('onNewsFilterHook');
|
|
794
|
+
expect(hookFile).toContain('onBeforeLoadModel');
|
|
795
|
+
expect(hookFile).toContain('onBeforeRender');
|
|
796
|
+
});
|
|
797
|
+
test('scaffoldFilter with save_filters option', () => {
|
|
798
|
+
const result = (0, filter_tool_1.scaffoldFilter)({
|
|
799
|
+
addon_name: 'articles',
|
|
800
|
+
fields: [{ field: 'author', type: 'text', label: 'Автор' }],
|
|
801
|
+
options: { save_filters: true },
|
|
802
|
+
});
|
|
803
|
+
expect('articles/saved_filters.php' in result.files).toBe(true);
|
|
804
|
+
const savedFile = result.files['articles/saved_filters.php'];
|
|
805
|
+
expect(savedFile).toContain('ArticlesSavedFilters');
|
|
806
|
+
expect(savedFile).toContain('function save(');
|
|
807
|
+
expect(savedFile).toContain('function load(');
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
describe('SEO Tool', () => {
|
|
811
|
+
test('scaffoldSeo generates SEO files', () => {
|
|
812
|
+
const result = (0, seo_tool_1.scaffoldSeo)({
|
|
813
|
+
addon_name: 'blog',
|
|
814
|
+
});
|
|
815
|
+
expect(result).toHaveProperty('addon_name', 'blog');
|
|
816
|
+
expect(result).toHaveProperty('fields_count', 3);
|
|
817
|
+
expect('blog/seo.php' in result.files).toBe(true);
|
|
818
|
+
expect('system/hooks/blog/seo.hooks.php' in result.files).toBe(true);
|
|
819
|
+
});
|
|
820
|
+
test('scaffoldSeo generates sitemap when enabled', () => {
|
|
821
|
+
const result = (0, seo_tool_1.scaffoldSeo)({
|
|
822
|
+
addon_name: 'news',
|
|
823
|
+
options: { use_sitemap: true },
|
|
824
|
+
});
|
|
825
|
+
expect('news/sitemap.php' in result.files).toBe(true);
|
|
826
|
+
const sitemap = result.files['news/sitemap.php'];
|
|
827
|
+
expect(sitemap).toContain('NewsSitemap');
|
|
828
|
+
expect(sitemap).toContain('function getItems(');
|
|
829
|
+
expect(sitemap).toContain('function generateXml(');
|
|
830
|
+
});
|
|
831
|
+
test('scaffoldSeo generates schema.org when enabled', () => {
|
|
832
|
+
const result = (0, seo_tool_1.scaffoldSeo)({
|
|
833
|
+
addon_name: 'articles',
|
|
834
|
+
options: { use_schema_org: true },
|
|
835
|
+
});
|
|
836
|
+
expect('articles/schema.php' in result.files).toBe(true);
|
|
837
|
+
const schema = result.files['articles/schema.php'];
|
|
838
|
+
expect(schema).toContain('ArticlesSchemaOrg');
|
|
839
|
+
expect(schema).toContain('function getArticleSchema(');
|
|
840
|
+
});
|
|
841
|
+
test('scaffoldSeo with custom fields', () => {
|
|
842
|
+
const result = (0, seo_tool_1.scaffoldSeo)({
|
|
843
|
+
addon_name: 'products',
|
|
844
|
+
fields: [
|
|
845
|
+
{ field: 'meta_title', type: 'title', value: '{item.name} - {site.name}' },
|
|
846
|
+
{ field: 'meta_desc', type: 'description', value: '{item.excerpt}' },
|
|
847
|
+
],
|
|
848
|
+
});
|
|
849
|
+
const seoFile = result.files['products/seo.php'];
|
|
850
|
+
expect(seoFile).toContain('ProductsSeo');
|
|
851
|
+
expect(seoFile).toContain('function getTitle(');
|
|
852
|
+
expect(seoFile).toContain('function getDescription(');
|
|
853
|
+
});
|
|
854
|
+
test('scaffoldSeo generates hook handlers', () => {
|
|
855
|
+
const result = (0, seo_tool_1.scaffoldSeo)({
|
|
856
|
+
addon_name: 'test_seo',
|
|
857
|
+
options: { use_og_tags: true, use_sitemap: true },
|
|
858
|
+
});
|
|
859
|
+
const hooks = result.files['system/hooks/test_seo/seo.hooks.php'];
|
|
860
|
+
expect(hooks).toContain('onTestSeoSeoHook');
|
|
861
|
+
expect(hooks).toContain('function onBeforeRender(');
|
|
862
|
+
expect(hooks).toContain('function onAfterSave(');
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
describe('Import/Export Tool', () => {
|
|
866
|
+
test('scaffoldImportExport generates import/export files', () => {
|
|
867
|
+
const result = (0, import_export_tool_1.scaffoldImportExport)({
|
|
868
|
+
addon_name: 'products',
|
|
869
|
+
fields: [
|
|
870
|
+
{ field: 'title', type: 'string', label: 'Название' },
|
|
871
|
+
{ field: 'price', type: 'number', label: 'Цена' },
|
|
872
|
+
],
|
|
873
|
+
});
|
|
874
|
+
expect(result).toHaveProperty('addon_name', 'products');
|
|
875
|
+
expect(result).toHaveProperty('fields_count', 2);
|
|
876
|
+
expect('products/import.php' in result.files).toBe(true);
|
|
877
|
+
expect('products/export.php' in result.files).toBe(true);
|
|
878
|
+
});
|
|
879
|
+
test('scaffoldImportExport generates import class with parsing', () => {
|
|
880
|
+
const result = (0, import_export_tool_1.scaffoldImportExport)({
|
|
881
|
+
addon_name: 'catalog',
|
|
882
|
+
fields: [
|
|
883
|
+
{ field: 'name', type: 'string', label: 'Наименование', required: true },
|
|
884
|
+
{ field: 'quantity', type: 'number', label: 'Количество' },
|
|
885
|
+
{ field: 'is_active', type: 'bool', label: 'Активен' },
|
|
886
|
+
],
|
|
887
|
+
});
|
|
888
|
+
const importFile = result.files['catalog/import.php'];
|
|
889
|
+
expect(importFile).toContain('CatalogImport');
|
|
890
|
+
expect(importFile).toContain('function importFromArray(');
|
|
891
|
+
expect(importFile).toContain('function importRow(');
|
|
892
|
+
expect(importFile).toContain('floatval');
|
|
893
|
+
});
|
|
894
|
+
test('scaffoldImportExport generates export class with formats', () => {
|
|
895
|
+
const result = (0, import_export_tool_1.scaffoldImportExport)({
|
|
896
|
+
addon_name: 'items',
|
|
897
|
+
fields: [{ field: 'title', type: 'string', label: 'Заголовок' }],
|
|
898
|
+
});
|
|
899
|
+
const exportFile = result.files['items/export.php'];
|
|
900
|
+
expect(exportFile).toContain('ItemsExport');
|
|
901
|
+
expect(exportFile).toContain('function exportToCsv(');
|
|
902
|
+
expect(exportFile).toContain('function exportToJson(');
|
|
903
|
+
expect(exportFile).toContain('function exportToXml(');
|
|
904
|
+
});
|
|
905
|
+
test('scaffoldImportExport with JSON API enabled', () => {
|
|
906
|
+
const result = (0, import_export_tool_1.scaffoldImportExport)({
|
|
907
|
+
addon_name: 'data_sync',
|
|
908
|
+
fields: [{ field: 'id', type: 'number', label: 'ID' }],
|
|
909
|
+
options: { use_json: true },
|
|
910
|
+
});
|
|
911
|
+
expect('data_sync/api.import.php' in result.files).toBe(true);
|
|
912
|
+
const apiFile = result.files['data_sync/api.import.php'];
|
|
913
|
+
expect(apiFile).toContain('DataSyncApiImport');
|
|
914
|
+
expect(apiFile).toContain('function actionImport(');
|
|
915
|
+
expect(apiFile).toContain('function actionExport(');
|
|
916
|
+
});
|
|
917
|
+
test('scaffoldImportExport with CSV form enabled', () => {
|
|
918
|
+
const result = (0, import_export_tool_1.scaffoldImportExport)({
|
|
919
|
+
addon_name: 'test_ie',
|
|
920
|
+
fields: [{ field: 'name', type: 'string', label: 'Название' }],
|
|
921
|
+
options: { use_csv: true },
|
|
922
|
+
});
|
|
923
|
+
expect('test_ie/import.form.php' in result.files).toBe(true);
|
|
924
|
+
const formFile = result.files['test_ie/import.form.php'];
|
|
925
|
+
expect(formFile).toContain('TestIeImportForm');
|
|
926
|
+
expect(formFile).toContain('fieldFile');
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
describe('Cache Tool', () => {
|
|
930
|
+
test('scaffoldCache generates cache files', () => {
|
|
931
|
+
const result = (0, cache_tool_1.scaffoldCache)({
|
|
932
|
+
addon_name: 'blog',
|
|
933
|
+
});
|
|
934
|
+
expect(result).toHaveProperty('addon_name', 'blog');
|
|
935
|
+
expect('blog/cache.php' in result.files).toBe(true);
|
|
936
|
+
expect('system/hooks/blog/cache.hooks.php' in result.files).toBe(true);
|
|
937
|
+
});
|
|
938
|
+
test('scaffoldCache generates cache class with methods', () => {
|
|
939
|
+
const result = (0, cache_tool_1.scaffoldCache)({
|
|
940
|
+
addon_name: 'content',
|
|
941
|
+
options: { default_ttl: 7200 },
|
|
942
|
+
});
|
|
943
|
+
const cacheFile = result.files['content/cache.php'];
|
|
944
|
+
expect(cacheFile).toContain('ContentCache');
|
|
945
|
+
expect(cacheFile).toContain('function get(');
|
|
946
|
+
expect(cacheFile).toContain('function set(');
|
|
947
|
+
expect(cacheFile).toContain('function delete(');
|
|
948
|
+
expect(cacheFile).toContain('function remember(');
|
|
949
|
+
});
|
|
950
|
+
test('scaffoldCache with tags enabled', () => {
|
|
951
|
+
const result = (0, cache_tool_1.scaffoldCache)({
|
|
952
|
+
addon_name: 'items',
|
|
953
|
+
options: { use_tags: true },
|
|
954
|
+
});
|
|
955
|
+
expect('items/cache.tags.php' in result.files).toBe(true);
|
|
956
|
+
const tagsFile = result.files['items/cache.tags.php'];
|
|
957
|
+
expect(tagsFile).toContain('ItemsCacheTags');
|
|
958
|
+
expect(tagsFile).toContain('function invalidateTag(');
|
|
959
|
+
expect(tagsFile).toContain('function flushAll(');
|
|
960
|
+
});
|
|
961
|
+
test('scaffoldCache generates hooks for cache invalidation', () => {
|
|
962
|
+
const result = (0, cache_tool_1.scaffoldCache)({
|
|
963
|
+
addon_name: 'test_cache',
|
|
964
|
+
options: { use_tags: true },
|
|
965
|
+
});
|
|
966
|
+
const hooksFile = result.files['system/hooks/test_cache/cache.hooks.php'];
|
|
967
|
+
expect(hooksFile).toContain('onTestCacheCacheHook');
|
|
968
|
+
expect(hooksFile).toContain('function onAfterSave(');
|
|
969
|
+
expect(hooksFile).toContain('function onAfterDelete(');
|
|
970
|
+
expect(hooksFile).toContain('function onClearCache(');
|
|
971
|
+
});
|
|
972
|
+
test('scaffoldCache with Redis backend', () => {
|
|
973
|
+
const result = (0, cache_tool_1.scaffoldCache)({
|
|
974
|
+
addon_name: 'fast_cache',
|
|
975
|
+
options: { use_redis: true, use_tags: true },
|
|
976
|
+
});
|
|
977
|
+
const cacheFile = result.files['fast_cache/cache.php'];
|
|
978
|
+
expect(cacheFile).toContain('redis');
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
describe('Webhook Tool', () => {
|
|
982
|
+
test('scaffoldWebhook generates webhook files', () => {
|
|
983
|
+
const result = (0, webhook_tool_1.scaffoldWebhook)({
|
|
984
|
+
addon_name: 'notifications',
|
|
985
|
+
events: ['user.registered', 'order.paid'],
|
|
986
|
+
});
|
|
987
|
+
expect(result).toHaveProperty('addon_name', 'notifications');
|
|
988
|
+
expect(result).toHaveProperty('events_count', 2);
|
|
989
|
+
expect('notifications/webhooks.php' in result.files).toBe(true);
|
|
990
|
+
expect('notifications/webhook.config.php' in result.files).toBe(true);
|
|
991
|
+
});
|
|
992
|
+
test('scaffoldWebhook generates webhook handler class', () => {
|
|
993
|
+
const result = (0, webhook_tool_1.scaffoldWebhook)({
|
|
994
|
+
addon_name: 'shop',
|
|
995
|
+
events: ['item.created', 'item.updated', 'item.deleted'],
|
|
996
|
+
});
|
|
997
|
+
const handlerFile = result.files['shop/webhooks.php'];
|
|
998
|
+
expect(handlerFile).toContain('ShopWebhook');
|
|
999
|
+
expect(handlerFile).toContain('function handle(');
|
|
1000
|
+
expect(handlerFile).toContain('handleitem_created');
|
|
1001
|
+
});
|
|
1002
|
+
test('scaffoldWebhook generates config file', () => {
|
|
1003
|
+
const result = (0, webhook_tool_1.scaffoldWebhook)({
|
|
1004
|
+
addon_name: 'test_wh',
|
|
1005
|
+
events: ['event.one', 'event.two'],
|
|
1006
|
+
options: { use_signature: true },
|
|
1007
|
+
});
|
|
1008
|
+
const configFile = result.files['test_wh/webhook.config.php'];
|
|
1009
|
+
expect(configFile).toContain('event.one');
|
|
1010
|
+
expect(configFile).toContain('event.two');
|
|
1011
|
+
expect(configFile).toContain('signature');
|
|
1012
|
+
});
|
|
1013
|
+
test('scaffoldWebhook with retry enabled generates queue', () => {
|
|
1014
|
+
const result = (0, webhook_tool_1.scaffoldWebhook)({
|
|
1015
|
+
addon_name: 'api_events',
|
|
1016
|
+
events: ['sync.data'],
|
|
1017
|
+
options: { use_retry: true, retry_count: 5 },
|
|
1018
|
+
});
|
|
1019
|
+
expect('api_events/webhook.queue.php' in result.files).toBe(true);
|
|
1020
|
+
const queueFile = result.files['api_events/webhook.queue.php'];
|
|
1021
|
+
expect(queueFile).toContain('ApiEventsWebhookQueue');
|
|
1022
|
+
expect(queueFile).toContain('function getPending(');
|
|
1023
|
+
expect(queueFile).toContain('function retry(');
|
|
1024
|
+
});
|
|
1025
|
+
test('scaffoldWebhook with signature enabled generates security', () => {
|
|
1026
|
+
const result = (0, webhook_tool_1.scaffoldWebhook)({
|
|
1027
|
+
addon_name: 'secure_webhook',
|
|
1028
|
+
events: ['payment.completed'],
|
|
1029
|
+
options: { use_signature: true },
|
|
1030
|
+
});
|
|
1031
|
+
expect('secure_webhook/webhook.security.php' in result.files).toBe(true);
|
|
1032
|
+
const securityFile = result.files['secure_webhook/webhook.security.php'];
|
|
1033
|
+
expect(securityFile).toContain('SecureWebhookWebhookSecurity');
|
|
1034
|
+
expect(securityFile).toContain('function verifySignature(');
|
|
1035
|
+
expect(securityFile).toContain('function generateSignature(');
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
describe('External API Tool', () => {
|
|
1039
|
+
test('scaffoldExternalApi generates API client files', () => {
|
|
1040
|
+
const result = (0, external_api_tool_1.scaffoldExternalApi)({
|
|
1041
|
+
addon_name: 'payment_api',
|
|
1042
|
+
base_url: 'https://api.payment.com/v1',
|
|
1043
|
+
endpoints: [
|
|
1044
|
+
{ path: '/payments', method: 'POST' },
|
|
1045
|
+
{ path: '/payments/{id}', method: 'GET' },
|
|
1046
|
+
],
|
|
1047
|
+
});
|
|
1048
|
+
expect(result).toHaveProperty('addon_name', 'payment_api');
|
|
1049
|
+
expect(result).toHaveProperty('base_url', 'https://api.payment.com/v1');
|
|
1050
|
+
expect(result).toHaveProperty('endpoints_count', 2);
|
|
1051
|
+
expect('payment_api/api/client.php' in result.files).toBe(true);
|
|
1052
|
+
expect('payment_api/api/request.php' in result.files).toBe(true);
|
|
1053
|
+
});
|
|
1054
|
+
test('scaffoldExternalApi generates API client with methods', () => {
|
|
1055
|
+
const result = (0, external_api_tool_1.scaffoldExternalApi)({
|
|
1056
|
+
addon_name: 'weather',
|
|
1057
|
+
base_url: 'https://api.weather.com',
|
|
1058
|
+
endpoints: [
|
|
1059
|
+
{ path: '/forecast', method: 'GET' },
|
|
1060
|
+
{ path: '/alerts', method: 'POST' },
|
|
1061
|
+
],
|
|
1062
|
+
});
|
|
1063
|
+
const clientFile = result.files['weather/api/client.php'];
|
|
1064
|
+
expect(clientFile).toContain('WeatherApiClient');
|
|
1065
|
+
expect(clientFile).toContain('function get_forecast(');
|
|
1066
|
+
expect(clientFile).toContain('function post_alerts(');
|
|
1067
|
+
});
|
|
1068
|
+
test('scaffoldExternalApi with auth enabled generates auth file', () => {
|
|
1069
|
+
const result = (0, external_api_tool_1.scaffoldExternalApi)({
|
|
1070
|
+
addon_name: 'secure_api',
|
|
1071
|
+
base_url: 'https://api.secure.com',
|
|
1072
|
+
endpoints: [{ path: '/data', method: 'GET' }],
|
|
1073
|
+
options: { use_auth: true, auth_type: 'bearer' },
|
|
1074
|
+
});
|
|
1075
|
+
expect('secure_api/api/auth.php' in result.files).toBe(true);
|
|
1076
|
+
const authFile = result.files['secure_api/api/auth.php'];
|
|
1077
|
+
expect(authFile).toContain('SecureApiApiAuth');
|
|
1078
|
+
expect(authFile).toContain('function getHeaders(');
|
|
1079
|
+
});
|
|
1080
|
+
test('scaffoldExternalApi with rate limit generates limiter', () => {
|
|
1081
|
+
const result = (0, external_api_tool_1.scaffoldExternalApi)({
|
|
1082
|
+
addon_name: 'limited_api',
|
|
1083
|
+
base_url: 'https://api.limited.com',
|
|
1084
|
+
endpoints: [{ path: '/search', method: 'GET' }],
|
|
1085
|
+
options: { use_rate_limit: true, rate_limit: 30 },
|
|
1086
|
+
});
|
|
1087
|
+
expect('limited_api/api/rate_limiter.php' in result.files).toBe(true);
|
|
1088
|
+
const limiterFile = result.files['limited_api/api/rate_limiter.php'];
|
|
1089
|
+
expect(limiterFile).toContain('LimitedApiApiRateLimiter');
|
|
1090
|
+
expect(limiterFile).toContain('function canMakeRequest(');
|
|
1091
|
+
});
|
|
1092
|
+
test('scaffoldExternalApi with cache enabled generates cache', () => {
|
|
1093
|
+
const result = (0, external_api_tool_1.scaffoldExternalApi)({
|
|
1094
|
+
addon_name: 'cached_api',
|
|
1095
|
+
base_url: 'https://api.cached.com',
|
|
1096
|
+
endpoints: [{ path: '/status', method: 'GET' }],
|
|
1097
|
+
options: { use_cache: true },
|
|
1098
|
+
});
|
|
1099
|
+
expect('cached_api/api/cache.php' in result.files).toBe(true);
|
|
1100
|
+
const cacheFile = result.files['cached_api/api/cache.php'];
|
|
1101
|
+
expect(cacheFile).toContain('CachedApiApiCache');
|
|
1102
|
+
expect(cacheFile).toContain('function get(');
|
|
1103
|
+
expect(cacheFile).toContain('function set(');
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
describe('OAuth Tool', () => {
|
|
1107
|
+
test('scaffoldOAuth generates OAuth files', () => {
|
|
1108
|
+
const result = (0, oauth_tool_1.scaffoldOAuth)({
|
|
1109
|
+
addon_name: 'social_login',
|
|
1110
|
+
providers: [
|
|
1111
|
+
{
|
|
1112
|
+
name: 'google',
|
|
1113
|
+
client_id: 'xxx',
|
|
1114
|
+
client_secret: 'yyy',
|
|
1115
|
+
auth_url: 'https://accounts.google.com/o/oauth2/auth',
|
|
1116
|
+
token_url: 'https://oauth2.googleapis.com/token',
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
});
|
|
1120
|
+
expect(result).toHaveProperty('addon_name', 'social_login');
|
|
1121
|
+
expect(result).toHaveProperty('providers_count', 1);
|
|
1122
|
+
expect('social_login/oauth/client.php' in result.files).toBe(true);
|
|
1123
|
+
expect('social_login/oauth/provider.php' in result.files).toBe(true);
|
|
1124
|
+
expect('social_login/oauth/callback.php' in result.files).toBe(true);
|
|
1125
|
+
});
|
|
1126
|
+
test('scaffoldOAuth generates OAuth client class', () => {
|
|
1127
|
+
const result = (0, oauth_tool_1.scaffoldOAuth)({
|
|
1128
|
+
addon_name: 'oauth_test',
|
|
1129
|
+
providers: [
|
|
1130
|
+
{
|
|
1131
|
+
name: 'vkontakte',
|
|
1132
|
+
client_id: '123',
|
|
1133
|
+
client_secret: '456',
|
|
1134
|
+
auth_url: 'https://oauth.vk.com/authorize',
|
|
1135
|
+
token_url: 'https://oauth.vk.com/access_token',
|
|
1136
|
+
},
|
|
1137
|
+
],
|
|
1138
|
+
});
|
|
1139
|
+
const clientFile = result.files['oauth_test/oauth/client.php'];
|
|
1140
|
+
expect(clientFile).toContain('OauthTestOAuthClient');
|
|
1141
|
+
expect(clientFile).toContain('function getProvider(');
|
|
1142
|
+
expect(clientFile).toContain('function getAuthUrl(');
|
|
1143
|
+
});
|
|
1144
|
+
test('scaffoldOAuth generates OAuth provider with token handling', () => {
|
|
1145
|
+
const result = (0, oauth_tool_1.scaffoldOAuth)({
|
|
1146
|
+
addon_name: 'secure_oauth',
|
|
1147
|
+
providers: [
|
|
1148
|
+
{
|
|
1149
|
+
name: 'facebook',
|
|
1150
|
+
client_id: 'xxx',
|
|
1151
|
+
client_secret: 'yyy',
|
|
1152
|
+
auth_url: 'https://facebook.com/v10.0/dialog/oauth',
|
|
1153
|
+
token_url: 'https://graph.facebook.com/v10.0/oauth/access_token',
|
|
1154
|
+
},
|
|
1155
|
+
],
|
|
1156
|
+
});
|
|
1157
|
+
const providerFile = result.files['secure_oauth/oauth/provider.php'];
|
|
1158
|
+
expect(providerFile).toContain('SecureOauthOAuthProvider');
|
|
1159
|
+
expect(providerFile).toContain('function getAuthorizationUrl(');
|
|
1160
|
+
expect(providerFile).toContain('function handleCallback(');
|
|
1161
|
+
expect(providerFile).toContain('function refreshToken(');
|
|
1162
|
+
});
|
|
1163
|
+
test('scaffoldOAuth with db storage generates storage class', () => {
|
|
1164
|
+
const result = (0, oauth_tool_1.scaffoldOAuth)({
|
|
1165
|
+
addon_name: 'db_oauth',
|
|
1166
|
+
providers: [
|
|
1167
|
+
{
|
|
1168
|
+
name: 'github',
|
|
1169
|
+
client_id: 'xxx',
|
|
1170
|
+
client_secret: 'yyy',
|
|
1171
|
+
auth_url: 'https://github.com/login/oauth/authorize',
|
|
1172
|
+
token_url: 'https://github.com/login/oauth/access_token',
|
|
1173
|
+
},
|
|
1174
|
+
],
|
|
1175
|
+
options: { store_tokens_in_db: true },
|
|
1176
|
+
});
|
|
1177
|
+
expect('db_oauth/oauth/storage.php' in result.files).toBe(true);
|
|
1178
|
+
const storageFile = result.files['db_oauth/oauth/storage.php'];
|
|
1179
|
+
expect(storageFile).toContain('DbOauthOAuthStorage');
|
|
1180
|
+
expect(storageFile).toContain('function storeTokens(');
|
|
1181
|
+
expect(storageFile).toContain('function getTokens(');
|
|
1182
|
+
expect(storageFile).toContain('function getValidToken(');
|
|
1183
|
+
});
|
|
1184
|
+
test('scaffoldOAuth generates hooks for login buttons', () => {
|
|
1185
|
+
const result = (0, oauth_tool_1.scaffoldOAuth)({
|
|
1186
|
+
addon_name: 'login_oauth',
|
|
1187
|
+
providers: [
|
|
1188
|
+
{
|
|
1189
|
+
name: 'google',
|
|
1190
|
+
client_id: 'xxx',
|
|
1191
|
+
client_secret: 'yyy',
|
|
1192
|
+
auth_url: 'https://accounts.google.com/o/oauth2/auth',
|
|
1193
|
+
token_url: 'https://oauth2.googleapis.com/token',
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
name: 'vkontakte',
|
|
1197
|
+
client_id: '123',
|
|
1198
|
+
client_secret: '456',
|
|
1199
|
+
auth_url: 'https://oauth.vk.com/authorize',
|
|
1200
|
+
token_url: 'https://oauth.vk.com/access_token',
|
|
1201
|
+
},
|
|
1202
|
+
],
|
|
1203
|
+
});
|
|
1204
|
+
const hooksFile = result.files['system/hooks/login_oauth/oauth.hooks.php'];
|
|
1205
|
+
expect(hooksFile).toContain('onLoginOauthOAuthHook');
|
|
1206
|
+
expect(hooksFile).toContain('btn-google');
|
|
1207
|
+
expect(hooksFile).toContain('btn-vkontakte');
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
describe('Component Tool', () => {
|
|
1211
|
+
test('scaffoldComponent generates component files', () => {
|
|
1212
|
+
const result = (0, component_tool_1.scaffoldComponent)({
|
|
1213
|
+
addon_name: 'my_component',
|
|
1214
|
+
});
|
|
1215
|
+
expect(result).toHaveProperty('addon_name', 'my_component');
|
|
1216
|
+
expect(result).toHaveProperty('controllers_count', 1);
|
|
1217
|
+
expect('my_component/manifest.json' in result.files).toBe(true);
|
|
1218
|
+
expect('my_component/backend.php' in result.files).toBe(true);
|
|
1219
|
+
expect('my_component/frontend.php' in result.files).toBe(true);
|
|
1220
|
+
expect('my_component/model.php' in result.files).toBe(true);
|
|
1221
|
+
});
|
|
1222
|
+
test('scaffoldComponent generates manifest with correct structure', () => {
|
|
1223
|
+
const result = (0, component_tool_1.scaffoldComponent)({
|
|
1224
|
+
addon_name: 'test_comp',
|
|
1225
|
+
controllers: [{ name: 'items', actions: ['index', 'view'], use_model: true }],
|
|
1226
|
+
});
|
|
1227
|
+
const manifest = result.files['test_comp/manifest.json'];
|
|
1228
|
+
expect(manifest).toContain("'type' => 'component'");
|
|
1229
|
+
expect(manifest).toContain("'name' => 'test_comp'");
|
|
1230
|
+
});
|
|
1231
|
+
test('scaffoldComponent with options generates additional files', () => {
|
|
1232
|
+
const result = (0, component_tool_1.scaffoldComponent)({
|
|
1233
|
+
addon_name: 'full_comp',
|
|
1234
|
+
options: { with_routes: true, with_menu: true },
|
|
1235
|
+
});
|
|
1236
|
+
expect('full_comp/routes.php' in result.files).toBe(true);
|
|
1237
|
+
expect('full_comp/menu.php' in result.files).toBe(true);
|
|
1238
|
+
});
|
|
1239
|
+
test('scaffoldComponent generates backend with controllers', () => {
|
|
1240
|
+
const result = (0, component_tool_1.scaffoldComponent)({
|
|
1241
|
+
addon_name: 'backend_test',
|
|
1242
|
+
controllers: [{ name: 'products', actions: ['index', 'add', 'edit'] }],
|
|
1243
|
+
});
|
|
1244
|
+
const backend = result.files['backend_test/backend.php'];
|
|
1245
|
+
expect(backend).toContain('BackendTestBackend');
|
|
1246
|
+
expect(backend).toContain('getBackendMenu');
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
describe('Widget Tool', () => {
|
|
1250
|
+
test('scaffoldWidget generates widget files', () => {
|
|
1251
|
+
const result = (0, widget_tool_1.scaffoldWidget)({
|
|
1252
|
+
addon_name: 'blog',
|
|
1253
|
+
widget_name: 'recent_posts',
|
|
1254
|
+
});
|
|
1255
|
+
expect(result).toHaveProperty('addon_name', 'blog');
|
|
1256
|
+
expect(result).toHaveProperty('widget_name', 'recent_posts');
|
|
1257
|
+
expect('blog/widgets/recent_posts.php' in result.files).toBe(true);
|
|
1258
|
+
expect('blog/widgets/recent_posts.options.php' in result.files).toBe(true);
|
|
1259
|
+
expect('blog/widgets/recent_posts.html.php' in result.files).toBe(true);
|
|
1260
|
+
});
|
|
1261
|
+
test('scaffoldWidget generates widget class with options', () => {
|
|
1262
|
+
const result = (0, widget_tool_1.scaffoldWidget)({
|
|
1263
|
+
addon_name: 'news',
|
|
1264
|
+
widget_name: 'latest_news',
|
|
1265
|
+
options: [
|
|
1266
|
+
{ name: 'title', type: 'text', label: 'Заголовок', default: 'Latest News' },
|
|
1267
|
+
{ name: 'limit', type: 'number', label: 'Количество', default: 5 },
|
|
1268
|
+
],
|
|
1269
|
+
});
|
|
1270
|
+
const widgetFile = result.files['news/widgets/latest_news.php'];
|
|
1271
|
+
expect(widgetFile).toContain('LatestNewsWidget');
|
|
1272
|
+
expect(widgetFile).toContain('getOptions');
|
|
1273
|
+
});
|
|
1274
|
+
test('scaffoldWidget with styles generates CSS', () => {
|
|
1275
|
+
const result = (0, widget_tool_1.scaffoldWidget)({
|
|
1276
|
+
addon_name: 'test_widget',
|
|
1277
|
+
widget_name: 'custom',
|
|
1278
|
+
options_config: { with_styles: true },
|
|
1279
|
+
});
|
|
1280
|
+
expect('test_widget/widgets/custom.css' in result.files).toBe(true);
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1283
|
+
describe('Template Theme Tool', () => {
|
|
1284
|
+
test('scaffoldTemplate generates template files', () => {
|
|
1285
|
+
const result = (0, template_tool_1.scaffoldTemplate)({
|
|
1286
|
+
template_name: 'my_theme',
|
|
1287
|
+
});
|
|
1288
|
+
expect(result).toHaveProperty('template_name', 'my_theme');
|
|
1289
|
+
expect(result).toHaveProperty('blocks_count', 5);
|
|
1290
|
+
expect('my_theme/manifest.json' in result.files).toBe(true);
|
|
1291
|
+
expect('my_theme/main.tpl.php' in result.files).toBe(true);
|
|
1292
|
+
expect('my_theme/main.css' in result.files).toBe(true);
|
|
1293
|
+
});
|
|
1294
|
+
test('scaffoldTemplate generates manifest with options', () => {
|
|
1295
|
+
const result = (0, template_tool_1.scaffoldTemplate)({
|
|
1296
|
+
template_name: 'dark_theme',
|
|
1297
|
+
options: { with_dark_mode: true, with_responsive: true },
|
|
1298
|
+
});
|
|
1299
|
+
const manifest = result.files['dark_theme/manifest.json'];
|
|
1300
|
+
expect(manifest).toContain('dark_mode');
|
|
1301
|
+
expect(manifest).toContain('responsive');
|
|
1302
|
+
});
|
|
1303
|
+
test('scaffoldTemplate with layout generates YAML', () => {
|
|
1304
|
+
const result = (0, template_tool_1.scaffoldTemplate)({
|
|
1305
|
+
template_name: 'layout_test',
|
|
1306
|
+
options: { with_layout: true },
|
|
1307
|
+
});
|
|
1308
|
+
expect('layout_test/layout.yaml' in result.files).toBe(true);
|
|
1309
|
+
});
|
|
1310
|
+
test('scaffoldTemplate generates header and footer templates', () => {
|
|
1311
|
+
const result = (0, template_tool_1.scaffoldTemplate)({
|
|
1312
|
+
template_name: 'test_template',
|
|
1313
|
+
});
|
|
1314
|
+
expect('test_template/header.tpl.php' in result.files).toBe(true);
|
|
1315
|
+
expect('test_template/footer.tpl.php' in result.files).toBe(true);
|
|
1316
|
+
});
|
|
1317
|
+
});
|
|
1318
|
+
describe('Layout Tool', () => {
|
|
1319
|
+
test('listLayoutPresets returns presets', () => {
|
|
1320
|
+
const result = (0, layout_tool_1.listLayoutPresets)();
|
|
1321
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1322
|
+
expect(result.length).toBeGreaterThan(0);
|
|
1323
|
+
expect(result[0]).toHaveProperty('name');
|
|
1324
|
+
});
|
|
1325
|
+
test('scaffoldLayoutScheme with custom rows', () => {
|
|
1326
|
+
const result = (0, layout_tool_1.scaffoldLayoutScheme)({
|
|
1327
|
+
template: 'modern',
|
|
1328
|
+
rows: [
|
|
1329
|
+
{
|
|
1330
|
+
title: 'Test Row',
|
|
1331
|
+
cols: [{ title: 'Test Col', class: 'col-md-12' }],
|
|
1332
|
+
},
|
|
1333
|
+
],
|
|
1334
|
+
});
|
|
1335
|
+
expect(result).toHaveProperty('yaml');
|
|
1336
|
+
expect(typeof result.yaml).toBe('string');
|
|
1337
|
+
});
|
|
1338
|
+
});
|
|
1339
|
+
describe('DB Tool', () => {
|
|
1340
|
+
test('introspectDatabase returns tables', () => {
|
|
1341
|
+
const result = (0, db_tool_1.introspectDatabase)();
|
|
1342
|
+
expect(result).toHaveProperty('totalTables');
|
|
1343
|
+
expect(result.totalTables).toBeGreaterThan(0);
|
|
1344
|
+
expect(result).toHaveProperty('tables');
|
|
1345
|
+
});
|
|
1346
|
+
test('introspectDatabase returns specific table', () => {
|
|
1347
|
+
const result = (0, db_tool_1.introspectDatabase)('users');
|
|
1348
|
+
expect(result).toHaveProperty('table');
|
|
1349
|
+
expect(result.table).toContain('cms_');
|
|
1350
|
+
});
|
|
1351
|
+
test('listContentTypes returns types', () => {
|
|
1352
|
+
const result = (0, db_tool_1.listContentTypes)();
|
|
1353
|
+
expect(result).toHaveProperty('table');
|
|
1354
|
+
expect(result).toHaveProperty('fields');
|
|
1355
|
+
});
|
|
1356
|
+
test('listDatabaseEvents returns events', () => {
|
|
1357
|
+
const result = (0, db_tool_1.listDatabaseEvents)();
|
|
1358
|
+
expect(result).toHaveProperty('totalEvents');
|
|
1359
|
+
expect(result.totalEvents).toBeGreaterThan(0);
|
|
1360
|
+
});
|
|
1361
|
+
test('describeTable returns table details', () => {
|
|
1362
|
+
const result = (0, db_tool_1.describeTable)('users');
|
|
1363
|
+
expect(result).toHaveProperty('table');
|
|
1364
|
+
expect(result).toHaveProperty('fields');
|
|
1365
|
+
expect(result).toHaveProperty('indexes');
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
describe('Controllers Tool', () => {
|
|
1369
|
+
test('analyzeController returns controller info', () => {
|
|
1370
|
+
const result = (0, controllers_tool_1.analyzeController)('content');
|
|
1371
|
+
expect(result).toHaveProperty('name', 'content');
|
|
1372
|
+
expect(result).toHaveProperty('className');
|
|
1373
|
+
expect(result).toHaveProperty('actions');
|
|
1374
|
+
});
|
|
1375
|
+
test('analyzeController returns backend', () => {
|
|
1376
|
+
const result = (0, controllers_tool_1.analyzeController)('admin', 'backend');
|
|
1377
|
+
expect(result).toHaveProperty('type', 'backend');
|
|
1378
|
+
});
|
|
1379
|
+
test('listControllers returns list', () => {
|
|
1380
|
+
const result = (0, controllers_tool_1.listControllers)();
|
|
1381
|
+
expect(result).toHaveProperty('total');
|
|
1382
|
+
expect(result.total).toBeGreaterThan(0);
|
|
1383
|
+
expect(result).toHaveProperty('allControllers');
|
|
1384
|
+
});
|
|
1385
|
+
test('listControllers with filter', () => {
|
|
1386
|
+
const result = (0, controllers_tool_1.listControllers)('frontend');
|
|
1387
|
+
expect(result).toHaveProperty('byType');
|
|
1388
|
+
expect(result.byType).toHaveProperty('frontend');
|
|
1389
|
+
});
|
|
1390
|
+
test('getControllerActionsList returns actions', () => {
|
|
1391
|
+
const result = (0, controllers_tool_1.getControllerActionsList)('content');
|
|
1392
|
+
expect(result).toHaveProperty('controller', 'content');
|
|
1393
|
+
expect(result).toHaveProperty('actions');
|
|
1394
|
+
});
|
|
1395
|
+
test('listSystemTraits returns traits', () => {
|
|
1396
|
+
const result = (0, controllers_tool_1.listSystemTraits)();
|
|
1397
|
+
expect(result).toHaveProperty('traits');
|
|
1398
|
+
expect(result.traits).toBeTruthy();
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
describe('Source Tool', () => {
|
|
1402
|
+
test('listWidgets returns widgets', () => {
|
|
1403
|
+
const result = (0, source_tool_1.listWidgets)();
|
|
1404
|
+
expect(result).toHaveProperty('total');
|
|
1405
|
+
expect(result.total).toBeGreaterThan(0);
|
|
1406
|
+
expect(result).toHaveProperty('widgets');
|
|
1407
|
+
});
|
|
1408
|
+
test('getWidgetInfo returns widget details', () => {
|
|
1409
|
+
const result = (0, source_tool_1.getWidgetInfo)('text');
|
|
1410
|
+
expect(result).toHaveProperty('name', 'text');
|
|
1411
|
+
expect(result).toHaveProperty('className');
|
|
1412
|
+
expect(result).toHaveProperty('optionsFormPath');
|
|
1413
|
+
});
|
|
1414
|
+
test('getWidgetInfo returns error for unknown widget', () => {
|
|
1415
|
+
const result = (0, source_tool_1.getWidgetInfo)('unknown_widget_xyz');
|
|
1416
|
+
expect(result).toHaveProperty('error');
|
|
1417
|
+
});
|
|
1418
|
+
test('listTraits returns traits', () => {
|
|
1419
|
+
const result = (0, source_tool_1.listTraits)();
|
|
1420
|
+
expect(result).toHaveProperty('traits');
|
|
1421
|
+
});
|
|
1422
|
+
test('getTraitInfo returns trait details', () => {
|
|
1423
|
+
const result = (0, source_tool_1.getTraitInfo)('fieldsParseable');
|
|
1424
|
+
expect(result).toHaveProperty('name', 'fieldsParseable');
|
|
1425
|
+
expect(result).toHaveProperty('methods');
|
|
1426
|
+
});
|
|
1427
|
+
test('listFields returns field types', () => {
|
|
1428
|
+
const result = (0, source_tool_1.listFields)();
|
|
1429
|
+
expect(result).toHaveProperty('total');
|
|
1430
|
+
expect(result.total).toBeGreaterThan(0);
|
|
1431
|
+
});
|
|
1432
|
+
test('getFieldInfo returns field details', () => {
|
|
1433
|
+
const result = (0, source_tool_1.getFieldInfo)('string');
|
|
1434
|
+
expect(result).toHaveProperty('name', 'string');
|
|
1435
|
+
expect(result).toHaveProperty('options');
|
|
1436
|
+
});
|
|
1437
|
+
test('getFieldInfo returns error for unknown field', () => {
|
|
1438
|
+
const result = (0, source_tool_1.getFieldInfo)('unknown_field_xyz');
|
|
1439
|
+
expect(result).toHaveProperty('error');
|
|
1440
|
+
});
|
|
1441
|
+
});
|
|
1442
|
+
describe('Migration Tool', () => {
|
|
1443
|
+
test('generateMigration generates install.php and SQL', () => {
|
|
1444
|
+
const result = (0, migration_tool_1.generateMigration)('test_items', [
|
|
1445
|
+
{ name: 'title', type: 'varchar(255)' },
|
|
1446
|
+
{ name: 'content', type: 'text' },
|
|
1447
|
+
]);
|
|
1448
|
+
expect(result).toHaveProperty('install_php');
|
|
1449
|
+
expect(result).toHaveProperty('sql');
|
|
1450
|
+
expect(result).toHaveProperty('table_name');
|
|
1451
|
+
});
|
|
1452
|
+
test('generateMigration with all options', () => {
|
|
1453
|
+
const result = (0, migration_tool_1.generateMigration)('products', [
|
|
1454
|
+
{
|
|
1455
|
+
name: 'title',
|
|
1456
|
+
type: 'varchar(255)',
|
|
1457
|
+
nullable: false,
|
|
1458
|
+
default: 'New',
|
|
1459
|
+
comment: 'Product title',
|
|
1460
|
+
},
|
|
1461
|
+
{ name: 'price', type: 'decimal(10,2)', nullable: true },
|
|
1462
|
+
{ name: 'created_at', type: 'datetime' },
|
|
1463
|
+
]);
|
|
1464
|
+
expect(result.sql).toContain('NOT NULL');
|
|
1465
|
+
expect(result.install_php).toContain('createTable');
|
|
1466
|
+
});
|
|
1467
|
+
test('generateFieldSuggestions returns suggestions', () => {
|
|
1468
|
+
const result = (0, migration_tool_1.generateFieldSuggestions)('string');
|
|
1469
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1470
|
+
expect(result.length).toBeGreaterThan(0);
|
|
1471
|
+
});
|
|
1472
|
+
test('generateFieldSuggestions for all types', () => {
|
|
1473
|
+
const types = ['string', 'text', 'number', 'datetime', 'user', 'bool'];
|
|
1474
|
+
types.forEach(type => {
|
|
1475
|
+
const result = (0, migration_tool_1.generateFieldSuggestions)(type);
|
|
1476
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1477
|
+
});
|
|
1478
|
+
});
|
|
1479
|
+
});
|
|
1480
|
+
describe('Requirement Tool', () => {
|
|
1481
|
+
test('analyzeRequirement returns analysis', () => {
|
|
1482
|
+
const result = (0, requirement_tool_1.analyzeRequirement)('каталог товаров');
|
|
1483
|
+
expect(result).toHaveProperty('addon_type');
|
|
1484
|
+
expect(result).toHaveProperty('features');
|
|
1485
|
+
expect(result).toHaveProperty('suggested_title');
|
|
1486
|
+
});
|
|
1487
|
+
test('suggestAddonStructure returns structure', () => {
|
|
1488
|
+
const result = (0, requirement_tool_1.suggestAddonStructure)('with_admin');
|
|
1489
|
+
expect(result).toHaveProperty('type', 'with_admin');
|
|
1490
|
+
expect(result).toHaveProperty('files');
|
|
1491
|
+
expect(result).toHaveProperty('description');
|
|
1492
|
+
});
|
|
1493
|
+
});
|
|
1494
|
+
const fs = require('fs');
|
|
1495
|
+
const SOURCE_PATH = './source/system/core/user.php';
|
|
1496
|
+
const sourceExists = fs.existsSync(SOURCE_PATH);
|
|
1497
|
+
const describeOrSkip = sourceExists ? describe : describe.skip;
|
|
1498
|
+
describeOrSkip('Core Parser', () => {
|
|
1499
|
+
test('parseCoreFile parses user.php correctly', () => {
|
|
1500
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1501
|
+
const result = parseCoreFile('./source/system/core/user.php');
|
|
1502
|
+
expect(result).not.toBeNull();
|
|
1503
|
+
expect(result.name).toBe('cmsUser');
|
|
1504
|
+
expect(result.file).toBe('user');
|
|
1505
|
+
expect(result.methods.length).toBeGreaterThan(0);
|
|
1506
|
+
});
|
|
1507
|
+
test('parseCoreFile parses database.php correctly', () => {
|
|
1508
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1509
|
+
const result = parseCoreFile('./source/system/core/database.php');
|
|
1510
|
+
expect(result).not.toBeNull();
|
|
1511
|
+
expect(result.name).toBe('cmsDatabase');
|
|
1512
|
+
expect(result.methods.length).toBeGreaterThan(40);
|
|
1513
|
+
});
|
|
1514
|
+
test('parseCoreFile parses config.php correctly', () => {
|
|
1515
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1516
|
+
const result = parseCoreFile('./source/system/core/config.php');
|
|
1517
|
+
expect(result).not.toBeNull();
|
|
1518
|
+
expect(result.name).toBe('cmsConfig');
|
|
1519
|
+
expect(result.extends).toBe('cmsConfigs');
|
|
1520
|
+
});
|
|
1521
|
+
test('parseCoreFile parses autoloader.php correctly', () => {
|
|
1522
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1523
|
+
const result = parseCoreFile('./source/system/core/autoloader.php');
|
|
1524
|
+
expect(result).not.toBeNull();
|
|
1525
|
+
expect(result.methods.length).toBeGreaterThan(0);
|
|
1526
|
+
});
|
|
1527
|
+
test('parseAllCoreFiles finds 38 classes', () => {
|
|
1528
|
+
const { parseAllCoreFiles } = require('../tools/parser/core-parser');
|
|
1529
|
+
const result = parseAllCoreFiles('./source');
|
|
1530
|
+
expect(result.length).toBe(38);
|
|
1531
|
+
});
|
|
1532
|
+
test('cmsUser has some public methods', () => {
|
|
1533
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1534
|
+
const result = parseCoreFile('./source/system/core/user.php');
|
|
1535
|
+
const publicMethods = result.methods.filter((m) => m.visibility === 'public');
|
|
1536
|
+
expect(publicMethods.length).toBeGreaterThan(0);
|
|
1537
|
+
});
|
|
1538
|
+
test('cmsDatabase has expected public methods', () => {
|
|
1539
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1540
|
+
const result = parseCoreFile('./source/system/core/database.php');
|
|
1541
|
+
const publicMethods = result.methods.filter((m) => m.visibility === 'public');
|
|
1542
|
+
expect(publicMethods.some((m) => m.name === 'query')).toBe(true);
|
|
1543
|
+
expect(publicMethods.some((m) => m.name === 'insert')).toBe(true);
|
|
1544
|
+
expect(publicMethods.some((m) => m.name === 'update')).toBe(true);
|
|
1545
|
+
expect(publicMethods.some((m) => m.name === 'delete')).toBe(true);
|
|
1546
|
+
});
|
|
1547
|
+
test('cmsPermissions has expected methods', () => {
|
|
1548
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1549
|
+
const result = parseCoreFile('./source/system/core/permissions.php');
|
|
1550
|
+
const publicMethods = result.methods.filter((m) => m.visibility === 'public');
|
|
1551
|
+
expect(publicMethods.some((m) => m.name === 'isAllowed')).toBe(true);
|
|
1552
|
+
expect(publicMethods.some((m) => m.name === 'isDenied')).toBe(true);
|
|
1553
|
+
});
|
|
1554
|
+
test('cmsPaginator has some methods', () => {
|
|
1555
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1556
|
+
const result = parseCoreFile('./source/system/core/paginator.php');
|
|
1557
|
+
expect(result.methods.length).toBeGreaterThan(0);
|
|
1558
|
+
});
|
|
1559
|
+
test('cmsResponse has some public methods', () => {
|
|
1560
|
+
const { parseCoreFile } = require('../tools/parser/core-parser');
|
|
1561
|
+
const result = parseCoreFile('./source/system/core/response.php');
|
|
1562
|
+
const publicMethods = result.methods.filter((m) => m.visibility === 'public');
|
|
1563
|
+
expect(publicMethods.length).toBeGreaterThan(0);
|
|
1564
|
+
expect(publicMethods.some((m) => m.name === 'send')).toBe(true);
|
|
1565
|
+
});
|
|
1566
|
+
test('all core classes have required properties', () => {
|
|
1567
|
+
const { parseAllCoreFiles } = require('../tools/parser/core-parser');
|
|
1568
|
+
const classes = parseAllCoreFiles('./source');
|
|
1569
|
+
classes.forEach((cls) => {
|
|
1570
|
+
expect(cls).toHaveProperty('name');
|
|
1571
|
+
expect(cls).toHaveProperty('file');
|
|
1572
|
+
expect(cls).toHaveProperty('methods');
|
|
1573
|
+
expect(cls).toHaveProperty('properties');
|
|
1574
|
+
expect(cls).toHaveProperty('constants');
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
test('coreClasses array has 38 entries', () => {
|
|
1578
|
+
const { coreClasses } = require('../data/core-api');
|
|
1579
|
+
expect(coreClasses.length).toBe(38);
|
|
1580
|
+
});
|
|
1581
|
+
test('coreAPIMap has all 38 classes', () => {
|
|
1582
|
+
const { coreAPIMap } = require('../data/core-api');
|
|
1583
|
+
expect(Object.keys(coreAPIMap).length).toBe(38);
|
|
1584
|
+
});
|
|
1585
|
+
test('cmsTemplate has many methods', () => {
|
|
1586
|
+
const { coreAPIMap } = require('../data/core-api');
|
|
1587
|
+
expect(coreAPIMap.cmsTemplate.methods.length).toBeGreaterThan(100);
|
|
1588
|
+
});
|
|
1589
|
+
test('cmsModel has many methods', () => {
|
|
1590
|
+
const { coreAPIMap } = require('../data/core-api');
|
|
1591
|
+
expect(coreAPIMap.cmsModel.methods.length).toBeGreaterThan(50);
|
|
1592
|
+
});
|
|
1593
|
+
test('cmsDatabase has many methods', () => {
|
|
1594
|
+
const { coreAPIMap } = require('../data/core-api');
|
|
1595
|
+
expect(coreAPIMap.cmsDatabase.methods.length).toBeGreaterThan(40);
|
|
1596
|
+
});
|
|
1597
|
+
test('coreAPIMap method signatures are valid strings', () => {
|
|
1598
|
+
const { coreAPIMap } = require('../data/core-api');
|
|
1599
|
+
for (const cls of Object.values(coreAPIMap)) {
|
|
1600
|
+
for (const method of cls.methods) {
|
|
1601
|
+
expect(typeof method.signature).toBe('string');
|
|
1602
|
+
expect(method.signature.length).toBeGreaterThan(0);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
describe('Libs API', () => {
|
|
1608
|
+
test('libsData has helpers, classes and thirdParty', () => {
|
|
1609
|
+
const { libsData } = require('../data/libs-api');
|
|
1610
|
+
expect(libsData).toHaveProperty('helpers');
|
|
1611
|
+
expect(libsData).toHaveProperty('classes');
|
|
1612
|
+
expect(libsData).toHaveProperty('thirdParty');
|
|
1613
|
+
});
|
|
1614
|
+
test('helpers has 4 modules', () => {
|
|
1615
|
+
const { libsData } = require('../data/libs-api');
|
|
1616
|
+
expect(Object.keys(libsData.helpers).length).toBe(4);
|
|
1617
|
+
expect(libsData.helpers).toHaveProperty('template');
|
|
1618
|
+
expect(libsData.helpers).toHaveProperty('html');
|
|
1619
|
+
expect(libsData.helpers).toHaveProperty('files');
|
|
1620
|
+
expect(libsData.helpers).toHaveProperty('strings');
|
|
1621
|
+
});
|
|
1622
|
+
test('template helper has many functions', () => {
|
|
1623
|
+
const { libsData } = require('../data/libs-api');
|
|
1624
|
+
expect(libsData.helpers.template.functions.length).toBeGreaterThan(20);
|
|
1625
|
+
});
|
|
1626
|
+
test('html helper has many functions', () => {
|
|
1627
|
+
const { libsData } = require('../data/libs-api');
|
|
1628
|
+
expect(libsData.helpers.html.functions.length).toBeGreaterThan(25);
|
|
1629
|
+
});
|
|
1630
|
+
test('files helper has many functions', () => {
|
|
1631
|
+
const { libsData } = require('../data/libs-api');
|
|
1632
|
+
expect(libsData.helpers.files.functions.length).toBeGreaterThan(10);
|
|
1633
|
+
});
|
|
1634
|
+
test('strings helper has many functions', () => {
|
|
1635
|
+
const { libsData } = require('../data/libs-api');
|
|
1636
|
+
expect(libsData.helpers.strings.functions.length).toBeGreaterThan(30);
|
|
1637
|
+
});
|
|
1638
|
+
test('classes has 6 class libraries', () => {
|
|
1639
|
+
const { libsData } = require('../data/libs-api');
|
|
1640
|
+
expect(Object.keys(libsData.classes).length).toBe(6);
|
|
1641
|
+
expect(libsData.classes).toHaveProperty('jevix');
|
|
1642
|
+
expect(libsData.classes).toHaveProperty('googleAuthenticator');
|
|
1643
|
+
expect(libsData.classes).toHaveProperty('mobileDetect');
|
|
1644
|
+
expect(libsData.classes).toHaveProperty('lastRSS');
|
|
1645
|
+
expect(libsData.classes).toHaveProperty('idnaConvert');
|
|
1646
|
+
expect(libsData.classes).toHaveProperty('spyc');
|
|
1647
|
+
});
|
|
1648
|
+
test('thirdParty has 3 libraries', () => {
|
|
1649
|
+
const { libsData } = require('../data/libs-api');
|
|
1650
|
+
expect(Object.keys(libsData.thirdParty).length).toBe(3);
|
|
1651
|
+
expect(libsData.thirdParty).toHaveProperty('scssphp');
|
|
1652
|
+
expect(libsData.thirdParty).toHaveProperty('geshi');
|
|
1653
|
+
expect(libsData.thirdParty).toHaveProperty('phpmailer');
|
|
1654
|
+
});
|
|
1655
|
+
});
|
|
1656
|
+
describe('Database Schema', () => {
|
|
1657
|
+
test('databaseSchema has all required tables', () => {
|
|
1658
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1659
|
+
expect(databaseSchema.tableCount).toBe(50);
|
|
1660
|
+
});
|
|
1661
|
+
test('databaseSchema has cms_users table', () => {
|
|
1662
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1663
|
+
const usersTable = databaseSchema.tables.find((t) => t.name === 'cms_users');
|
|
1664
|
+
expect(usersTable).toBeDefined();
|
|
1665
|
+
expect(usersTable.fields.length).toBeGreaterThan(10);
|
|
1666
|
+
});
|
|
1667
|
+
test('databaseSchema has cms_content_types table', () => {
|
|
1668
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1669
|
+
const ctTable = databaseSchema.tables.find((t) => t.name === 'cms_content_types');
|
|
1670
|
+
expect(ctTable).toBeDefined();
|
|
1671
|
+
});
|
|
1672
|
+
test('all tables have name and fields', () => {
|
|
1673
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1674
|
+
databaseSchema.tables.forEach((table) => {
|
|
1675
|
+
expect(table).toHaveProperty('name');
|
|
1676
|
+
expect(table).toHaveProperty('fields');
|
|
1677
|
+
expect(Array.isArray(table.fields)).toBe(true);
|
|
1678
|
+
});
|
|
1679
|
+
});
|
|
1680
|
+
test('all tables have indexes', () => {
|
|
1681
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1682
|
+
databaseSchema.tables.forEach((table) => {
|
|
1683
|
+
expect(table).toHaveProperty('indexes');
|
|
1684
|
+
expect(Array.isArray(table.indexes)).toBe(true);
|
|
1685
|
+
});
|
|
1686
|
+
});
|
|
1687
|
+
});
|
|
1688
|
+
describe('Events Map', () => {
|
|
1689
|
+
test('eventsMap has 95 events', () => {
|
|
1690
|
+
const { eventsMap } = require('../data/events-map');
|
|
1691
|
+
expect(eventsMap.eventCount).toBe(95);
|
|
1692
|
+
});
|
|
1693
|
+
test('eventsMap has byEvent lookup', () => {
|
|
1694
|
+
const { eventsMap } = require('../data/events-map');
|
|
1695
|
+
expect(eventsMap.byEvent).toBeDefined();
|
|
1696
|
+
expect(typeof eventsMap.byEvent).toBe('object');
|
|
1697
|
+
});
|
|
1698
|
+
test('eventsMap has byController lookup', () => {
|
|
1699
|
+
const { eventsMap } = require('../data/events-map');
|
|
1700
|
+
expect(eventsMap.byController).toBeDefined();
|
|
1701
|
+
});
|
|
1702
|
+
test('events have required properties', () => {
|
|
1703
|
+
const { eventsMap } = require('../data/events-map');
|
|
1704
|
+
eventsMap.events.slice(0, 10).forEach((event) => {
|
|
1705
|
+
expect(event).toHaveProperty('id');
|
|
1706
|
+
expect(event).toHaveProperty('event');
|
|
1707
|
+
expect(event).toHaveProperty('listener');
|
|
1708
|
+
});
|
|
1709
|
+
});
|
|
1710
|
+
});
|
|
1711
|
+
describe('Widgets Map', () => {
|
|
1712
|
+
test('widgetsMap has 4 system widgets', () => {
|
|
1713
|
+
const { widgetsMap } = require('../data/widgets-map');
|
|
1714
|
+
expect(widgetsMap.widgetCount).toBe(4);
|
|
1715
|
+
});
|
|
1716
|
+
test('widgetsMap has text, menu, html, template', () => {
|
|
1717
|
+
const { widgetsMap } = require('../data/widgets-map');
|
|
1718
|
+
expect(widgetsMap.byName).toHaveProperty('text');
|
|
1719
|
+
expect(widgetsMap.byName).toHaveProperty('menu');
|
|
1720
|
+
expect(widgetsMap.byName).toHaveProperty('html');
|
|
1721
|
+
expect(widgetsMap.byName).toHaveProperty('template');
|
|
1722
|
+
});
|
|
1723
|
+
test('getWidget function works', () => {
|
|
1724
|
+
const { getWidget } = require('../data/widgets-map');
|
|
1725
|
+
const textWidget = getWidget('text');
|
|
1726
|
+
expect(textWidget).toBeDefined();
|
|
1727
|
+
expect(textWidget.name).toBe('text');
|
|
1728
|
+
});
|
|
1729
|
+
test('all widgets have className', () => {
|
|
1730
|
+
const { widgetsMap } = require('../data/widgets-map');
|
|
1731
|
+
widgetsMap.widgets.forEach((widget) => {
|
|
1732
|
+
expect(widget).toHaveProperty('className');
|
|
1733
|
+
expect(widget.className).toMatch(/^widget/);
|
|
1734
|
+
});
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1737
|
+
describe('Traits Map', () => {
|
|
1738
|
+
test('traitsMap has traits', () => {
|
|
1739
|
+
const { traitsMap } = require('../data/traits-map');
|
|
1740
|
+
expect(traitsMap.traits.length).toBeGreaterThan(5);
|
|
1741
|
+
});
|
|
1742
|
+
test('traitsMap has byNamespace', () => {
|
|
1743
|
+
const { traitsMap } = require('../data/traits-map');
|
|
1744
|
+
expect(traitsMap.byNamespace).toBeDefined();
|
|
1745
|
+
expect(Object.keys(traitsMap.byNamespace).length).toBeGreaterThan(0);
|
|
1746
|
+
});
|
|
1747
|
+
test('listgrid trait exists', () => {
|
|
1748
|
+
const { traitsMap } = require('../data/traits-map');
|
|
1749
|
+
const listgrid = traitsMap.traits.find((t) => t.name === 'listgrid');
|
|
1750
|
+
expect(listgrid).toBeDefined();
|
|
1751
|
+
});
|
|
1752
|
+
test('all traits have methods', () => {
|
|
1753
|
+
const { traitsMap } = require('../data/traits-map');
|
|
1754
|
+
traitsMap.traits.forEach((trait) => {
|
|
1755
|
+
expect(trait).toHaveProperty('methods');
|
|
1756
|
+
expect(Array.isArray(trait.methods)).toBe(true);
|
|
1757
|
+
});
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
1760
|
+
describe('Fields Map', () => {
|
|
1761
|
+
test('fieldsMap has 31+ field types', () => {
|
|
1762
|
+
const { fieldsMap } = require('../data/fields-map');
|
|
1763
|
+
expect(fieldsMap.fieldCount).toBeGreaterThan(30);
|
|
1764
|
+
});
|
|
1765
|
+
test('fieldsMap has string, number, list fields', () => {
|
|
1766
|
+
const { fieldsMap } = require('../data/fields-map');
|
|
1767
|
+
expect(fieldsMap.byName).toHaveProperty('string');
|
|
1768
|
+
expect(fieldsMap.byName).toHaveProperty('number');
|
|
1769
|
+
expect(fieldsMap.byName).toHaveProperty('list');
|
|
1770
|
+
});
|
|
1771
|
+
test('fields have options array', () => {
|
|
1772
|
+
const { fieldsMap } = require('../data/fields-map');
|
|
1773
|
+
const stringField = fieldsMap.byName['string'];
|
|
1774
|
+
expect(stringField.options).toBeDefined();
|
|
1775
|
+
expect(Array.isArray(stringField.options)).toBe(true);
|
|
1776
|
+
});
|
|
1777
|
+
test('fields have validationRules', () => {
|
|
1778
|
+
const { fieldsMap } = require('../data/fields-map');
|
|
1779
|
+
const numberField = fieldsMap.byName['number'];
|
|
1780
|
+
expect(numberField.validationRules).toBeDefined();
|
|
1781
|
+
});
|
|
1782
|
+
});
|
|
1783
|
+
describe('Controllers Map', () => {
|
|
1784
|
+
test('controllersMap has 61+ controllers', () => {
|
|
1785
|
+
const { controllersMap } = require('../data/controllers-map');
|
|
1786
|
+
expect(controllersMap.controllerCount).toBeGreaterThan(60);
|
|
1787
|
+
});
|
|
1788
|
+
test('controllersMap has activity_frontend, admin_frontend, content_frontend', () => {
|
|
1789
|
+
const { controllersMap } = require('../data/controllers-map');
|
|
1790
|
+
expect(controllersMap.byName).toHaveProperty('activity_frontend');
|
|
1791
|
+
expect(controllersMap.byName).toHaveProperty('admin_frontend');
|
|
1792
|
+
expect(controllersMap.byName).toHaveProperty('content_frontend');
|
|
1793
|
+
});
|
|
1794
|
+
test('content_frontend controller has many actions', () => {
|
|
1795
|
+
const { controllersMap } = require('../data/controllers-map');
|
|
1796
|
+
const content = controllersMap.byName['content_frontend'];
|
|
1797
|
+
expect(content).toBeDefined();
|
|
1798
|
+
expect(content.actions.length).toBeGreaterThan(5);
|
|
1799
|
+
});
|
|
1800
|
+
test('controllers have frontend and backend', () => {
|
|
1801
|
+
const { controllersMap } = require('../data/controllers-map');
|
|
1802
|
+
const activity = controllersMap.controllers.filter((c) => c.name === 'activity');
|
|
1803
|
+
expect(activity.length).toBeGreaterThanOrEqual(2);
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
describe('Components API', () => {
|
|
1807
|
+
test('components array has cmsModel', () => {
|
|
1808
|
+
const { components } = require('../data/components');
|
|
1809
|
+
const cmsModel = components.find((c) => c.name === 'cmsModel');
|
|
1810
|
+
expect(cmsModel).toBeDefined();
|
|
1811
|
+
expect(cmsModel.methods.length).toBeGreaterThan(30);
|
|
1812
|
+
});
|
|
1813
|
+
test('components have required properties', () => {
|
|
1814
|
+
const { components } = require('../data/components');
|
|
1815
|
+
components.forEach((comp) => {
|
|
1816
|
+
expect(comp).toHaveProperty('name');
|
|
1817
|
+
expect(comp).toHaveProperty('class');
|
|
1818
|
+
expect(comp).toHaveProperty('methods');
|
|
1819
|
+
});
|
|
1820
|
+
});
|
|
1821
|
+
test('cmsModel has filterEqual method', () => {
|
|
1822
|
+
const { components } = require('../data/components');
|
|
1823
|
+
const cmsModel = components.find((c) => c.name === 'cmsModel');
|
|
1824
|
+
const filterEqual = cmsModel.methods.find((m) => m.name === 'filterEqual');
|
|
1825
|
+
expect(filterEqual).toBeDefined();
|
|
1826
|
+
});
|
|
1827
|
+
});
|
|
1828
|
+
describe('Hooks Data', () => {
|
|
1829
|
+
test('hooks array includes source-backed and curated hooks', () => {
|
|
1830
|
+
const { hooks } = require('../data/hooks');
|
|
1831
|
+
expect(hooks.length).toBeGreaterThan(250);
|
|
1832
|
+
expect(hooks.some((hook) => hook.source?.files?.length > 0)).toBe(true);
|
|
1833
|
+
});
|
|
1834
|
+
test('source-backed components include provenance', () => {
|
|
1835
|
+
const { components } = require('../data/components');
|
|
1836
|
+
const cmsModel = components.find((component) => component.name === 'cmsModel');
|
|
1837
|
+
expect(cmsModel.source.file).toBe('system/core/model.php');
|
|
1838
|
+
expect(cmsModel.source.repository).toContain('instantsoft/icms2');
|
|
1839
|
+
});
|
|
1840
|
+
test('hooks have required properties', () => {
|
|
1841
|
+
const { hooks } = require('../data/hooks');
|
|
1842
|
+
hooks.forEach((hook) => {
|
|
1843
|
+
expect(hook).toHaveProperty('name');
|
|
1844
|
+
expect(hook).toHaveProperty('type');
|
|
1845
|
+
expect(hook).toHaveProperty('category');
|
|
1846
|
+
expect(hook).toHaveProperty('parameters');
|
|
1847
|
+
});
|
|
1848
|
+
});
|
|
1849
|
+
test('content_after_add_approve hook exists', () => {
|
|
1850
|
+
const { hooks } = require('../data/hooks');
|
|
1851
|
+
const hook = hooks.find((h) => h.name === 'content_after_add_approve');
|
|
1852
|
+
expect(hook).toBeDefined();
|
|
1853
|
+
expect(hook.type).toBe('action');
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1856
|
+
describe('Routes Map', () => {
|
|
1857
|
+
test('routesMap has content and photos controllers', () => {
|
|
1858
|
+
const { routesMap } = require('../data/routes-map');
|
|
1859
|
+
expect(routesMap.controllers.length).toBe(2);
|
|
1860
|
+
expect(routesMap.routeCount).toBeGreaterThan(30);
|
|
1861
|
+
});
|
|
1862
|
+
test('content controller has many routes', () => {
|
|
1863
|
+
const { getRoutesByController } = require('../data/routes-map');
|
|
1864
|
+
const content = getRoutesByController('content');
|
|
1865
|
+
expect(content).toBeDefined();
|
|
1866
|
+
expect(content.routes.length).toBeGreaterThan(20);
|
|
1867
|
+
});
|
|
1868
|
+
test('all routes have pattern and action', () => {
|
|
1869
|
+
const { routesMap } = require('../data/routes-map');
|
|
1870
|
+
routesMap.controllers.forEach((ctrl) => {
|
|
1871
|
+
ctrl.routes.forEach((route) => {
|
|
1872
|
+
expect(route).toHaveProperty('pattern');
|
|
1873
|
+
expect(route).toHaveProperty('action');
|
|
1874
|
+
});
|
|
1875
|
+
});
|
|
1876
|
+
});
|
|
1877
|
+
});
|
|
1878
|
+
describe('Zod Validation', () => {
|
|
1879
|
+
const { validateHook, validateComponent, validateField, validateWidget, validateTrait, validateTable, validateEvent, validateController, validateRoute, validateDatabaseSchema, validateEventsMap, validateControllersMap, validateRoutesMap, validateCoreClass, } = require('../data/schemas-validation');
|
|
1880
|
+
test('validateHook accepts valid hook', () => {
|
|
1881
|
+
const { hooks } = require('../data/hooks');
|
|
1882
|
+
const result = validateHook(hooks[0]);
|
|
1883
|
+
expect(result.success).toBe(true);
|
|
1884
|
+
});
|
|
1885
|
+
test('validateHook rejects invalid data', () => {
|
|
1886
|
+
const result = validateHook({ name: 123 });
|
|
1887
|
+
expect(result.success).toBe(false);
|
|
1888
|
+
});
|
|
1889
|
+
test('validateComponent accepts valid component', () => {
|
|
1890
|
+
const { components } = require('../data/components');
|
|
1891
|
+
const result = validateComponent(components[0]);
|
|
1892
|
+
expect(result.success).toBe(true);
|
|
1893
|
+
});
|
|
1894
|
+
test('validateComponent rejects invalid data', () => {
|
|
1895
|
+
const result = validateComponent({ name: 123 });
|
|
1896
|
+
expect(result.success).toBe(false);
|
|
1897
|
+
});
|
|
1898
|
+
test('validateField accepts valid field', () => {
|
|
1899
|
+
const { fieldsMap } = require('../data/fields-map');
|
|
1900
|
+
const result = validateField(fieldsMap.fields[0]);
|
|
1901
|
+
expect(result.success).toBe(true);
|
|
1902
|
+
});
|
|
1903
|
+
test('validateField rejects invalid data', () => {
|
|
1904
|
+
const result = validateField({ name: 123 });
|
|
1905
|
+
expect(result.success).toBe(false);
|
|
1906
|
+
});
|
|
1907
|
+
test('validateWidget accepts valid widget', () => {
|
|
1908
|
+
const { widgetsMap } = require('../data/widgets-map');
|
|
1909
|
+
const result = validateWidget(widgetsMap.widgets[0]);
|
|
1910
|
+
expect(result.success).toBe(true);
|
|
1911
|
+
});
|
|
1912
|
+
test('validateWidget rejects invalid data', () => {
|
|
1913
|
+
const result = validateWidget({ name: 123 });
|
|
1914
|
+
expect(result.success).toBe(false);
|
|
1915
|
+
});
|
|
1916
|
+
test('validateTrait accepts valid trait', () => {
|
|
1917
|
+
const { traitsMap } = require('../data/traits-map');
|
|
1918
|
+
const result = validateTrait(traitsMap.traits[0]);
|
|
1919
|
+
expect(result.success).toBe(true);
|
|
1920
|
+
});
|
|
1921
|
+
test('validateTrait rejects invalid data', () => {
|
|
1922
|
+
const result = validateTrait({ name: 123 });
|
|
1923
|
+
expect(result.success).toBe(false);
|
|
1924
|
+
});
|
|
1925
|
+
test('validateTable accepts valid table', () => {
|
|
1926
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1927
|
+
const result = validateTable(databaseSchema.tables[0]);
|
|
1928
|
+
expect(result.success).toBe(true);
|
|
1929
|
+
});
|
|
1930
|
+
test('validateTable rejects invalid data', () => {
|
|
1931
|
+
const result = validateTable({ name: 123 });
|
|
1932
|
+
expect(result.success).toBe(false);
|
|
1933
|
+
});
|
|
1934
|
+
test('validateEvent accepts valid event', () => {
|
|
1935
|
+
const { eventsMap } = require('../data/events-map');
|
|
1936
|
+
const result = validateEvent(eventsMap.events[0]);
|
|
1937
|
+
expect(result.success).toBe(true);
|
|
1938
|
+
});
|
|
1939
|
+
test('validateEvent rejects invalid data', () => {
|
|
1940
|
+
const result = validateEvent({ id: 'not-a-number' });
|
|
1941
|
+
expect(result.success).toBe(false);
|
|
1942
|
+
});
|
|
1943
|
+
test('validateController accepts valid controller', () => {
|
|
1944
|
+
const { controllersMap } = require('../data/controllers-map');
|
|
1945
|
+
const result = validateController(controllersMap.controllers[0]);
|
|
1946
|
+
expect(result.success).toBe(true);
|
|
1947
|
+
});
|
|
1948
|
+
test('validateController rejects invalid data', () => {
|
|
1949
|
+
const result = validateController({ name: 123 });
|
|
1950
|
+
expect(result.success).toBe(false);
|
|
1951
|
+
});
|
|
1952
|
+
test('validateRoute accepts valid route', () => {
|
|
1953
|
+
const { routesMap } = require('../data/routes-map');
|
|
1954
|
+
const result = validateRoute(routesMap.controllers[0].routes[0]);
|
|
1955
|
+
expect(result.success).toBe(true);
|
|
1956
|
+
});
|
|
1957
|
+
test('validateRoute rejects invalid data', () => {
|
|
1958
|
+
const result = validateRoute({ pattern: 123 });
|
|
1959
|
+
expect(result.success).toBe(false);
|
|
1960
|
+
});
|
|
1961
|
+
test('validateDatabaseSchema accepts valid schema', () => {
|
|
1962
|
+
const { databaseSchema } = require('../data/database-schema');
|
|
1963
|
+
const result = validateDatabaseSchema(databaseSchema);
|
|
1964
|
+
expect(result.success).toBe(true);
|
|
1965
|
+
});
|
|
1966
|
+
test('validateDatabaseSchema rejects invalid data', () => {
|
|
1967
|
+
const result = validateDatabaseSchema({ tables: 'not-array' });
|
|
1968
|
+
expect(result.success).toBe(false);
|
|
1969
|
+
});
|
|
1970
|
+
test('validateEventsMap accepts valid map', () => {
|
|
1971
|
+
const { eventsMap } = require('../data/events-map');
|
|
1972
|
+
const result = validateEventsMap(eventsMap);
|
|
1973
|
+
expect(result.success).toBe(true);
|
|
1974
|
+
});
|
|
1975
|
+
test('validateEventsMap rejects invalid data', () => {
|
|
1976
|
+
const result = validateEventsMap({ events: 'not-array' });
|
|
1977
|
+
expect(result.success).toBe(false);
|
|
1978
|
+
});
|
|
1979
|
+
test('validateControllersMap accepts valid map', () => {
|
|
1980
|
+
const { controllersMap } = require('../data/controllers-map');
|
|
1981
|
+
const result = validateControllersMap(controllersMap);
|
|
1982
|
+
expect(result.success).toBe(true);
|
|
1983
|
+
});
|
|
1984
|
+
test('validateControllersMap rejects invalid data', () => {
|
|
1985
|
+
const result = validateControllersMap({ controllers: 'not-array' });
|
|
1986
|
+
expect(result.success).toBe(false);
|
|
1987
|
+
});
|
|
1988
|
+
test('validateRoutesMap accepts valid map', () => {
|
|
1989
|
+
const { routesMap } = require('../data/routes-map');
|
|
1990
|
+
const result = validateRoutesMap(routesMap);
|
|
1991
|
+
expect(result.success).toBe(true);
|
|
1992
|
+
});
|
|
1993
|
+
test('validateRoutesMap rejects invalid data', () => {
|
|
1994
|
+
const result = validateRoutesMap({ controllers: 'not-array' });
|
|
1995
|
+
expect(result.success).toBe(false);
|
|
1996
|
+
});
|
|
1997
|
+
test('validateCoreClass accepts valid class', () => {
|
|
1998
|
+
const { coreClasses } = require('../data/core-api');
|
|
1999
|
+
const result = validateCoreClass(coreClasses[0]);
|
|
2000
|
+
expect(result.success).toBe(true);
|
|
2001
|
+
});
|
|
2002
|
+
test('validateCoreClass rejects invalid data', () => {
|
|
2003
|
+
const result = validateCoreClass({ name: 123 });
|
|
2004
|
+
expect(result.success).toBe(false);
|
|
2005
|
+
});
|
|
2006
|
+
});
|
|
2007
|
+
describe('List Routes Tool', () => {
|
|
2008
|
+
const { listRoutes } = require('../tools/source-tool');
|
|
2009
|
+
test('listRoutes returns all routes without filter', () => {
|
|
2010
|
+
const result = listRoutes();
|
|
2011
|
+
expect(result).toHaveProperty('total');
|
|
2012
|
+
expect(result).toHaveProperty('controllersCount');
|
|
2013
|
+
expect(result.total).toBeGreaterThan(30);
|
|
2014
|
+
});
|
|
2015
|
+
test('listRoutes returns routes for content controller', () => {
|
|
2016
|
+
const result = listRoutes('content');
|
|
2017
|
+
expect(result).toHaveProperty('controller', 'content');
|
|
2018
|
+
expect(result).toHaveProperty('routes');
|
|
2019
|
+
expect(result.routes.length).toBeGreaterThan(20);
|
|
2020
|
+
});
|
|
2021
|
+
test('listRoutes returns routes for photos controller', () => {
|
|
2022
|
+
const result = listRoutes('photos');
|
|
2023
|
+
expect(result).toHaveProperty('controller', 'photos');
|
|
2024
|
+
expect(result).toHaveProperty('routes');
|
|
2025
|
+
expect(result.routes.length).toBe(2);
|
|
2026
|
+
});
|
|
2027
|
+
test('listRoutes returns error for unknown controller', () => {
|
|
2028
|
+
const result = listRoutes('unknown_controller');
|
|
2029
|
+
expect(result).toHaveProperty('error');
|
|
2030
|
+
});
|
|
2031
|
+
});
|
|
2032
|
+
describe('Hooks Tool Extended', () => {
|
|
2033
|
+
const { listHooks, getHookDetails, searchHooks } = require('../tools/hooks-tool');
|
|
2034
|
+
test('listHooks returns all hooks by default', () => {
|
|
2035
|
+
const result = listHooks();
|
|
2036
|
+
expect(result.total).toBeGreaterThan(250);
|
|
2037
|
+
});
|
|
2038
|
+
test('listHooks filters by category engine', () => {
|
|
2039
|
+
const result = listHooks('engine');
|
|
2040
|
+
expect(result.total).toBe(9);
|
|
2041
|
+
result.hooks.forEach((h) => {
|
|
2042
|
+
expect(h.category).toBe('engine');
|
|
2043
|
+
});
|
|
2044
|
+
});
|
|
2045
|
+
test('listHooks filters by type action', () => {
|
|
2046
|
+
const result = listHooks(undefined, 'action');
|
|
2047
|
+
expect(result.total).toBeGreaterThan(40);
|
|
2048
|
+
result.hooks.forEach((h) => {
|
|
2049
|
+
expect(h.type).toMatch(/action/);
|
|
2050
|
+
});
|
|
2051
|
+
});
|
|
2052
|
+
test('getHookDetails returns content_after_add_approve', () => {
|
|
2053
|
+
const result = getHookDetails('content_after_add_approve');
|
|
2054
|
+
expect(result.name).toBe('content_after_add_approve');
|
|
2055
|
+
expect(result.type).toBe('action');
|
|
2056
|
+
});
|
|
2057
|
+
test('getHookDetails returns html_filter', () => {
|
|
2058
|
+
const result = getHookDetails('html_filter');
|
|
2059
|
+
expect(result.name).toBe('html_filter');
|
|
2060
|
+
expect(result.type).toBe('filter');
|
|
2061
|
+
});
|
|
2062
|
+
test('searchHooks finds hooks by keyword', () => {
|
|
2063
|
+
const result = searchHooks('user');
|
|
2064
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
describe('Components Tool Extended', () => {
|
|
2068
|
+
const { getAddonStructure, getComponentApi, listComponents, getFieldTypes, } = require('../tools/addon-tool');
|
|
2069
|
+
test('getAddonStructure basic type', () => {
|
|
2070
|
+
const result = getAddonStructure('basic');
|
|
2071
|
+
expect(result.type).toBe('basic');
|
|
2072
|
+
expect(result.files.length).toBeGreaterThan(0);
|
|
2073
|
+
});
|
|
2074
|
+
test('getAddonStructure with_admin type', () => {
|
|
2075
|
+
const result = getAddonStructure('with_admin');
|
|
2076
|
+
expect(result.type).toBe('with_admin');
|
|
2077
|
+
});
|
|
2078
|
+
test('listComponents returns components object', () => {
|
|
2079
|
+
const result = listComponents();
|
|
2080
|
+
expect(result).toHaveProperty('total');
|
|
2081
|
+
expect(result).toHaveProperty('components');
|
|
2082
|
+
expect(result.total).toBeGreaterThan(0);
|
|
2083
|
+
});
|
|
2084
|
+
test('getComponentApi returns cmsModel', () => {
|
|
2085
|
+
const result = getComponentApi('cmsModel');
|
|
2086
|
+
expect(result).toHaveProperty('name', 'cmsModel');
|
|
2087
|
+
expect(result.methods.length).toBeGreaterThan(30);
|
|
2088
|
+
});
|
|
2089
|
+
test('getComponentApi returns cmsTemplate', () => {
|
|
2090
|
+
const result = getComponentApi('cmsTemplate');
|
|
2091
|
+
expect(result).toHaveProperty('name', 'cmsTemplate');
|
|
2092
|
+
});
|
|
2093
|
+
test('getComponentApi returns error for unknown', () => {
|
|
2094
|
+
const result = getComponentApi('unknownClass');
|
|
2095
|
+
expect(result).toHaveProperty('error');
|
|
2096
|
+
});
|
|
2097
|
+
test('getFieldTypes returns field types object', () => {
|
|
2098
|
+
const result = getFieldTypes();
|
|
2099
|
+
expect(result).toHaveProperty('total');
|
|
2100
|
+
expect(result).toHaveProperty('field_types');
|
|
2101
|
+
expect(result.total).toBeGreaterThan(0);
|
|
2102
|
+
});
|
|
2103
|
+
});
|
|
2104
|
+
describe('Scaffold Tool', () => {
|
|
2105
|
+
const { scaffoldAddon, scaffoldTemplate } = require('../tools/scaffold-tool');
|
|
2106
|
+
test('scaffoldAddon returns files object', () => {
|
|
2107
|
+
const result = scaffoldAddon({
|
|
2108
|
+
name: 'test_addon',
|
|
2109
|
+
title: 'Test Addon',
|
|
2110
|
+
});
|
|
2111
|
+
expect(result).toHaveProperty('addon_name');
|
|
2112
|
+
expect(result).toHaveProperty('files');
|
|
2113
|
+
expect(result.addon_name).toBe('test_addon');
|
|
2114
|
+
expect(result.files_count).toBeGreaterThan(0);
|
|
2115
|
+
});
|
|
2116
|
+
test('scaffoldAddon includes backend for with_admin type', () => {
|
|
2117
|
+
const result = scaffoldAddon({
|
|
2118
|
+
name: 'test_addon',
|
|
2119
|
+
title: 'Test Addon',
|
|
2120
|
+
type: 'with_admin',
|
|
2121
|
+
});
|
|
2122
|
+
expect(result.type).toBe('with_admin');
|
|
2123
|
+
const backendKey = Object.keys(result.files).find(k => k.endsWith('backend.php'));
|
|
2124
|
+
expect(backendKey).toBeTruthy();
|
|
2125
|
+
});
|
|
2126
|
+
test('scaffoldAddon includes hooks if specified', () => {
|
|
2127
|
+
const result = scaffoldAddon({
|
|
2128
|
+
name: 'test_addon',
|
|
2129
|
+
title: 'Test Addon',
|
|
2130
|
+
hooks: ['content_after_add_approve'],
|
|
2131
|
+
});
|
|
2132
|
+
expect(JSON.stringify(result.files)).toContain('content_after_add_approve');
|
|
2133
|
+
});
|
|
2134
|
+
test('scaffoldTemplate returns template structure', () => {
|
|
2135
|
+
const result = scaffoldTemplate({
|
|
2136
|
+
name: 'test_template',
|
|
2137
|
+
title: 'Test Template',
|
|
2138
|
+
});
|
|
2139
|
+
expect(result).toHaveProperty('template_name');
|
|
2140
|
+
expect(result).toHaveProperty('files');
|
|
2141
|
+
expect(result.template_name).toBe('test_template');
|
|
2142
|
+
});
|
|
2143
|
+
});
|