@tbox.cn/app-toolkit 0.1.0 → 0.3.0
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/README.md +2 -2
- package/dist/{chunk-VV3ERZXS.js → chunk-KPD3LUH4.js} +1 -1
- package/dist/contracts-resolver-E4SECBOG.js +1 -0
- package/dist/index.d.ts +234 -43
- package/dist/index.js +8 -8
- package/package.json +2 -2
- package/src/assembly/app-manifest.ts +6 -0
- package/src/assembly/provider-catalog.ts +6 -1
- package/src/assembly/walk-manifests.ts +2 -1
- package/src/core/contracts-resolver.ts +3 -2
- package/src/core/credential-format.ts +4 -2
- package/src/core/errors.ts +9 -3
- package/src/dto.ts +127 -15
- package/src/factory.ts +129 -16
- package/src/index.ts +15 -4
- package/src/integrations/credentials.ts +1 -1
- package/src/integrations/domain-binding.ts +81 -0
- package/src/integrations/predicate.ts +7 -6
- package/src/integrations/read.ts +23 -3
- package/src/integrations/write.ts +93 -34
- package/src/views/app.ts +7 -5
- package/src/views/context.ts +3 -6
- package/src/views/credential-types.ts +28 -0
- package/src/views/credentials.ts +57 -0
- package/src/views/integration-schemas.ts +226 -0
- package/src/views/modules.ts +33 -14
- package/src/views/providers.ts +37 -22
- package/src/views/service-detail.ts +3 -4
- package/src/views/service-resolutions.ts +31 -25
- package/tests/credentials-view.test.ts +152 -0
- package/tests/demo-app.ts +14 -1
- package/tests/file-cache.test.ts +3 -1
- package/tests/naming-alignment.test.ts +8 -5
- package/tests/vendor-schema-keywords.test.ts +101 -0
- package/tests/views.test.ts +1285 -13
- package/tests/write-core.test.ts +107 -8
- package/dist/contracts-resolver-QM4FBQQV.js +0 -1
package/tests/write-core.test.ts
CHANGED
|
@@ -22,10 +22,16 @@ interface Harness {
|
|
|
22
22
|
credFile(stem: string): string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function harness(opts: { rejectMarker?: string } = {}): Harness {
|
|
25
|
+
function harness(opts: { rejectMarker?: string; zodRejectPaths?: string[][] } = {}): Harness {
|
|
26
26
|
const app = createTempApp('tbox-write-core-');
|
|
27
27
|
const store = join(app.appDir, 'contracts-store');
|
|
28
|
-
writeFakeContractsPackage(store, {
|
|
28
|
+
writeFakeContractsPackage(store, {
|
|
29
|
+
version: '0.9.0',
|
|
30
|
+
withStrict: true,
|
|
31
|
+
rejectMarker: opts.rejectMarker,
|
|
32
|
+
zodRejectPaths: opts.zodRejectPaths,
|
|
33
|
+
slots: ['parking.query'],
|
|
34
|
+
});
|
|
29
35
|
linkContracts(app.appDir, store, { subdir: 'apps/server' });
|
|
30
36
|
writeAppFile(app.appDir, INTEGRATIONS_FILE, JSON.stringify({ services: {} }, null, 2));
|
|
31
37
|
const tk = createAppToolkit(app.appDir);
|
|
@@ -67,10 +73,10 @@ describe('写核心管线(FX-1c)', () => {
|
|
|
67
73
|
// 回读逐字等价(GET = PUT = 落盘物)
|
|
68
74
|
const app = await h.tk.loadApp();
|
|
69
75
|
expect(app.integration).toEqual(node);
|
|
70
|
-
// dryRun + 不同节点 →
|
|
76
|
+
// dryRun + 不同节点 → 不落盘;files = 将触碰预览(D35——与真实写逐字对称)
|
|
71
77
|
const r2 = await h.tk.writeAppIntegration({ provider: 'changed' } as never, { dryRun: true });
|
|
72
78
|
expect(r2.written).toBe(false);
|
|
73
|
-
expect(r2.files).toEqual([]);
|
|
79
|
+
expect(r2.files).toEqual([INTEGRATIONS_FILE]);
|
|
74
80
|
expect(h.readConfig().provider).toBe('mock-x');
|
|
75
81
|
});
|
|
76
82
|
|
|
@@ -96,13 +102,13 @@ describe('写核心管线(FX-1c)', () => {
|
|
|
96
102
|
expect(readFileSync(h.credFile('joycity-parking.query'), 'utf8')).toContain('ROTATED-SECOND');
|
|
97
103
|
});
|
|
98
104
|
|
|
99
|
-
it('T3 dryRun + credentials(B3 钉死):零落盘 + credentials[] 预览 + files
|
|
105
|
+
it('T3 dryRun + credentials(B3 钉死):零落盘 + credentials[] 预览 + files = 双路径预览(D35)', async () => {
|
|
100
106
|
const r = await h.tk.writeServiceIntegration('parking.query', { provider: 'joycity' }, {
|
|
101
107
|
dryRun: true,
|
|
102
108
|
credentials: { type: 'api-key', values: { apiKey: 'SECRET-DRYRUN' } },
|
|
103
109
|
});
|
|
104
110
|
expect(r.written).toBe(false);
|
|
105
|
-
expect(r.files).toEqual([]);
|
|
111
|
+
expect(r.files).toEqual([INTEGRATIONS_FILE, 'config/credentials/joycity-parking.query.json']);
|
|
106
112
|
expect(r.credentials).toEqual([{ stem: 'joycity-parking.query', ref: 'secret://joycity-parking.query', overwrote: false }]);
|
|
107
113
|
// 凭据目录与 integrations.json 均零变更
|
|
108
114
|
expect(existsSync(h.credFile('joycity-parking.query'))).toBe(false);
|
|
@@ -119,21 +125,114 @@ describe('写核心管线(FX-1c)', () => {
|
|
|
119
125
|
credentials: { type: 'api-key', values: { apiKey: 'SHOULD-NOT-WRITE' } },
|
|
120
126
|
});
|
|
121
127
|
expect(r.credentials[0].overwrote).toBe(true);
|
|
128
|
+
// D35:no-diff + 同 stem → 仅凭据路径入预览(integrations 节点无变更不入清单)
|
|
129
|
+
expect(r.files).toEqual(['config/credentials/joycity-parking.query.json']);
|
|
122
130
|
expect(readFileSync(h.credFile('joycity-parking.query'), 'utf8')).toBe(before);
|
|
123
131
|
});
|
|
124
132
|
|
|
125
|
-
it('
|
|
133
|
+
it('T-sym dryRun ≡ 真实写(D35 对称性):同 body files 逐字相等 + written 相反 + dryRun 腿磁盘零变更', async () => {
|
|
134
|
+
const node = { provider: 'joycity', config: { baseUrl: 'https://sym.example' } };
|
|
135
|
+
const dry = await h.tk.writeServiceIntegration('parking.query', node, {
|
|
136
|
+
dryRun: true,
|
|
137
|
+
credentials: { type: 'api-key', values: { apiKey: 'SYM-DRY' } },
|
|
138
|
+
});
|
|
139
|
+
expect(dry.written).toBe(false);
|
|
140
|
+
// dryRun 零副作用(integrations 与凭据均零落盘)→ 免重置 harness 直接接真实写
|
|
141
|
+
expect(existsSync(h.credFile('joycity-parking.query'))).toBe(false);
|
|
142
|
+
expect((h.readConfig().services as Record<string, unknown>)['parking.query']).toBeUndefined();
|
|
143
|
+
const real = await h.tk.writeServiceIntegration('parking.query', node, {
|
|
144
|
+
credentials: { type: 'api-key', values: { apiKey: 'SYM-DRY' } },
|
|
145
|
+
});
|
|
146
|
+
expect(real.written).toBe(true);
|
|
147
|
+
expect(real.restartScheduled).toBe(false); // 工厂未注入 onApplied
|
|
148
|
+
// 对称性核心:同 body → files 逐字相等(integrations + 凭据工件双路径)
|
|
149
|
+
expect(dry.files).toEqual(real.files);
|
|
150
|
+
expect(dry.files).toEqual([INTEGRATIONS_FILE, 'config/credentials/joycity-parking.query.json']);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('T5 严格校验失败次序(A1):400 VALIDATION_FAILED + message 直通 + 凭据零落盘 + integrations 零变更', async () => {
|
|
126
154
|
h.app.dispose();
|
|
127
155
|
h = harness({ rejectMarker: '__reject__' });
|
|
128
156
|
await expect(
|
|
129
157
|
h.tk.writeServiceIntegration('parking.query', { provider: 'joycity', __reject__: true } as never, {
|
|
130
158
|
credentials: { type: 'api-key', values: { apiKey: 'X' } },
|
|
131
159
|
}),
|
|
132
|
-
).rejects.
|
|
160
|
+
).rejects.toMatchObject({
|
|
161
|
+
code: 'VALIDATION_FAILED',
|
|
162
|
+
httpStatus: 400,
|
|
163
|
+
message: expect.stringContaining('fake strict reject'),
|
|
164
|
+
});
|
|
133
165
|
expect(existsSync(h.credFile('joycity-parking.query'))).toBe(false);
|
|
134
166
|
expect((h.readConfig().services as Record<string, unknown>)['parking.query']).toBeUndefined();
|
|
135
167
|
});
|
|
136
168
|
|
|
169
|
+
it('T9 zod 形拒绝(A1):400 + fields 节点相对前缀剥离(applyService)', async () => {
|
|
170
|
+
h.app.dispose();
|
|
171
|
+
h = harness({ zodRejectPaths: [['services', 'parking.query', 'config', 'x']] });
|
|
172
|
+
await expect(
|
|
173
|
+
h.tk.writeServiceIntegration('parking.query', { provider: 'joycity', config: { x: 1 } }),
|
|
174
|
+
).rejects.toMatchObject({ code: 'VALIDATION_FAILED', httpStatus: 400, fields: ['config.x'] });
|
|
175
|
+
expect(existsSync(h.credFile('joycity-parking.query'))).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('T10 zod 形拒绝 root 级(A1):applyApp fields 原样(无前缀可剥)', async () => {
|
|
179
|
+
h.app.dispose();
|
|
180
|
+
h = harness({ zodRejectPaths: [['config', 'requestTimeoutMs']] });
|
|
181
|
+
await expect(
|
|
182
|
+
h.tk.writeAppIntegration({ provider: 'mock-x', config: { requestTimeoutMs: 1 } } as never),
|
|
183
|
+
).rejects.toMatchObject({ code: 'VALIDATION_FAILED', httpStatus: 400, fields: ['config.requestTimeoutMs'] });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('T11 敌对服务名(A2):路径参数不在词汇 → 404(身份判据先于 deriveStem)', async () => {
|
|
187
|
+
await expect(
|
|
188
|
+
h.tk.writeServiceIntegration('a/b', { provider: 'joycity' }, { credentials: { type: 'api-key', values: { apiKey: 'X' } } }),
|
|
189
|
+
).rejects.toMatchObject({ code: 'SERVICE_NOT_FOUND', httpStatus: 404 });
|
|
190
|
+
await expect(h.tk.deleteServiceIntegration('a/b')).rejects.toMatchObject({ code: 'SERVICE_NOT_FOUND', httpStatus: 404 });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('T11b 敌对 provider 值位(第一道防线对照):400 + fields', async () => {
|
|
194
|
+
await expect(
|
|
195
|
+
h.tk.writeServiceIntegration('parking.query', { provider: 'a/b' }, { credentials: { type: 'api-key', values: { apiKey: 'X' } } }),
|
|
196
|
+
).rejects.toMatchObject({ code: 'VALIDATION_FAILED', httpStatus: 400, fields: ['root.provider'] });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('T12 credentials 缺 type(A1):400 fields credentials.type(无声明厂商亦要求非空字符串)', async () => {
|
|
200
|
+
await expect(
|
|
201
|
+
h.tk.writeServiceIntegration('parking.query', { provider: 'joycity' }, { credentials: { type: '', values: { apiKey: 'X' } } }),
|
|
202
|
+
).rejects.toMatchObject({ code: 'VALIDATION_FAILED', httpStatus: 400, fields: ['credentials.type'] });
|
|
203
|
+
await expect(
|
|
204
|
+
h.tk.writeAppIntegration({ provider: 'p-x' } as never, {
|
|
205
|
+
credentialsByInstance: { 'm-1': { values: { apiKey: 'X' } } },
|
|
206
|
+
} as never),
|
|
207
|
+
).rejects.toMatchObject({ code: 'VALIDATION_FAILED', httpStatus: 400, fields: ['credentials.type'] });
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('W1 未知服务 PUT/DELETE 404(A2):词汇 ∪ 已配置判据外', async () => {
|
|
211
|
+
await expect(h.tk.writeServiceIntegration('typo.query', { provider: 'mock' })).rejects.toMatchObject({
|
|
212
|
+
code: 'SERVICE_NOT_FOUND',
|
|
213
|
+
httpStatus: 404,
|
|
214
|
+
});
|
|
215
|
+
await expect(h.tk.deleteServiceIntegration('typo.query')).rejects.toMatchObject({
|
|
216
|
+
code: 'SERVICE_NOT_FOUND',
|
|
217
|
+
httpStatus: 404,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('W2 已配置孤儿键 PUT/DELETE 200(A2 判据含已配置——词汇外可继续写/删)', async () => {
|
|
222
|
+
// 预置孤儿键(不在词汇)+ 节点变更(避开 no-diff 短路——本用例锚身份判据非幂等语义)
|
|
223
|
+
writeAppFile(h.app.appDir, INTEGRATIONS_FILE, JSON.stringify({ services: { 'orphan.key': { provider: 'mock', config: { a: 1 } } } }, null, 2));
|
|
224
|
+
const r = await h.tk.writeServiceIntegration('orphan.key', { provider: 'mock' });
|
|
225
|
+
expect(r.written).toBe(true);
|
|
226
|
+
const d = await h.tk.deleteServiceIntegration('orphan.key');
|
|
227
|
+
expect(d.written).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('W3 词汇成员 DELETE 缺席节点:幂等 written:false(幂等语义仅判据内成员保留)', async () => {
|
|
231
|
+
const r = await h.tk.deleteServiceIntegration('parking.query');
|
|
232
|
+
expect(r.written).toBe(false);
|
|
233
|
+
expect(r.files).toEqual([]);
|
|
234
|
+
});
|
|
235
|
+
|
|
137
236
|
it('T6 恶意 map 键 400(F4):instances id / slot instances 键 / credentialsByInstance 键 / ByInstance 键', async () => {
|
|
138
237
|
// root.instances[].id
|
|
139
238
|
await expect(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as a,c as b}from"./chunk-VV3ERZXS.js";export{b as invalidateContractsResolver,a as resolveContractsForApp};
|