Bieber b4cb680f53
refactor: ai sdk 6 (#2118)
* chore: update tracing details

* feat: add attachemnt ro

* feat: upgrade to zod 4 and ai-sdk 5

* chore: openapi -> meta

* feat: ai-sdk 6

* chore: standard resource type

* feat: grid view state subscriber

* feat: pick upgrade node to 22

* fix: remove swagger to makesure backend start

* fix: remove swagger to makesure backend start

* chore: fix typecheck

* chore: clean ui effect

* chore: i18n for tools

* fix: card ui

* chore: update ai sdk provider

* fix: should not delete the last view in table

* feat: better zod error

* chore: clean base card

* fix: restore into base logic

* chore: i18n for tools

* chore: i18n for tools

* fix: swagger is back

* fix: pnpm lock

* fix: unit test

* fix: remove token

* fix: typecheck

* feat: proxy setting

* chore: update i18n for chat

* chore: tools i18n

* chore: upgrade ai sdk

* chore: i18n for ai gateway

* chore: rename app generation to app builder

* chore: remove deprecated version

* feat: adjust feature limitations

* chore: tools i18n

* fix: lint issue

* fix: api tag

* chore: zod i18n

* chore: add credit limit exceeded error handling

---------

Co-authored-by: SkyHuang <sky.huang.fe@gmail.com>
2025-12-09 14:43:29 +08:00

167 lines
5.3 KiB
TypeScript

import type { INestApplication } from '@nestjs/common';
import type { ICreatePluginRo, IGetPluginCenterListVo } from '@teable/openapi';
import {
createPlugin,
createPluginVoSchema,
deletePlugin,
getPlugin,
getPluginCenterList,
getPluginCenterListVoSchema,
getPlugins,
getPluginsVoSchema,
getPluginVoSchema,
PLUGIN_CENTER_GET_LIST,
PluginPosition,
PluginStatus,
publishPlugin,
submitPlugin,
updatePlugin,
} from '@teable/openapi';
import { createNewUserAxios } from './utils/axios-instance/new-user';
import { getError } from './utils/get-error';
import { initApp } from './utils/init-app';
const mockPlugin: ICreatePluginRo = {
name: 'plugin',
logo: '/plugin/xxxxxxx',
description: 'desc',
detailDesc: 'detail',
helpUrl: 'https://help.com',
positions: [PluginPosition.Dashboard],
i18n: {
en: {
name: 'plugin',
description: 'desc',
detailDesc: 'detail',
},
},
autoCreateMember: true,
};
describe('PluginController', () => {
let app: INestApplication;
let pluginId: string;
beforeAll(async () => {
const appCtx = await initApp();
app = appCtx.app;
});
beforeEach(async () => {
const res = await createPlugin(mockPlugin);
pluginId = res.data.id;
});
afterEach(async () => {
await deletePlugin(pluginId);
});
afterAll(async () => {
await app.close();
});
it('/api/plugin (POST)', async () => {
const res = await createPlugin(mockPlugin);
expect(createPluginVoSchema.strict().safeParse(res.data).success).toBe(true);
expect(res.data.status).toBe(PluginStatus.Developing);
expect(res.data.pluginUser).not.toBeUndefined();
await deletePlugin(res.data.id);
});
it('/api/plugin/{pluginId} (GET)', async () => {
const getRes = await getPlugin(pluginId);
expect(getPluginVoSchema.strict().safeParse(getRes.data).success).toBe(true);
expect(getRes.data.status).toBe(PluginStatus.Developing);
expect(getRes.data.pluginUser).not.toBeUndefined();
expect(getRes.data.pluginUser?.name).toEqual('plugin');
});
it('/api/plugin/{pluginId} (GET) - 404', async () => {
const error = await getError(() => getPlugin('invalid-id'));
expect(error?.status).toBe(404);
});
it('/api/plugin (GET)', async () => {
const getRes = await getPlugins();
expect(getPluginsVoSchema.safeParse(getRes.data).success).toBe(true);
expect(getRes.data).toHaveLength(3);
});
it('/api/plugin/{pluginId} (DELETE)', async () => {
const res = await createPlugin(mockPlugin);
await deletePlugin(res.data.id);
const error = await getError(() => getPlugin(res.data.id));
expect(error?.status).toBe(404);
});
it('/api/plugin/{pluginId} (PUT)', async () => {
const res = await createPlugin(mockPlugin);
const updatePluginRo = {
name: 'updated',
description: 'updated',
detailDesc: 'updated',
helpUrl: 'https://updated.com',
logo: 'https://updated.com/plugin/updated',
positions: [PluginPosition.Dashboard],
i18n: {
en: {
name: 'updated',
description: 'updated',
detailDesc: 'updated',
},
},
};
const putRes = await updatePlugin(res.data.id, updatePluginRo);
await deletePlugin(res.data.id);
expect(putRes.data.name).toBe(updatePluginRo.name);
expect(putRes.data.description).toBe(updatePluginRo.description);
expect(putRes.data.detailDesc).toBe(updatePluginRo.detailDesc);
expect(putRes.data.helpUrl).toBe(updatePluginRo.helpUrl);
expect(putRes.data.logo).toEqual(expect.stringContaining('plugin/updated'));
expect(putRes.data.i18n).toEqual(updatePluginRo.i18n);
});
it('/api/plugin/{pluginId}/submit (POST)', async () => {
const res = await createPlugin(mockPlugin);
const submitRes = await submitPlugin(res.data.id);
await deletePlugin(res.data.id);
expect(submitRes.status).toBe(200);
});
it('/api/admin/plugin/{pluginId}/publish (PATCH)', async () => {
const res = await createPlugin(mockPlugin);
await submitPlugin(res.data.id);
await publishPlugin(res.data.id);
const getRes = await getPlugin(res.data.id);
await deletePlugin(res.data.id);
expect(getRes.data.status).toBe(PluginStatus.Published);
});
it('/api/plugin/center/list (GET)', async () => {
const preList = await getPluginCenterList();
const res = await createPlugin(mockPlugin);
const postList = await getPluginCenterList();
await deletePlugin(res.data.id);
expect(postList.data).toHaveLength(preList.data.length + 1);
expect(
postList.data.find((p) => p.status === PluginStatus.Developing && p.id === res.data.id)
).not.toBeUndefined();
expect(getPluginCenterListVoSchema.safeParse(preList.data).success).toBe(true);
});
it('/api/plugin/center/list (GET) - 404', async () => {
const preList = await getPluginCenterList(mockPlugin.positions);
const res = await createPlugin(mockPlugin);
const newUserAxios = await createNewUserAxios({
email: 'plugin-center-list@test.com',
password: '12345678',
});
const plugins = await newUserAxios.get<IGetPluginCenterListVo>(PLUGIN_CENTER_GET_LIST, {
params: {
positions: JSON.stringify(mockPlugin.positions),
},
});
await deletePlugin(res.data.id);
expect(plugins.data).toHaveLength(preList.data.length - 1);
expect(plugins.data.some((p) => p.id === res.data.id)).toBe(false);
});
});