test: main data source e2e test (#3816)
* test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: test * test: test * refactor: code improve * test: test
This commit is contained in:
		
							parent
							
								
									467699cdf9
								
							
						
					
					
						commit
						44e7f48f99
					
				@ -17,7 +17,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface {
 | 
				
			|||||||
      'x-validator': 'uuid',
 | 
					      'x-validator': 'uuid',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  availableTypes = ['string', 'uid'];
 | 
					  availableTypes = ['string', 'uid', 'uuid'];
 | 
				
			||||||
  properties = {
 | 
					  properties = {
 | 
				
			||||||
    'uiSchema.title': {
 | 
					    'uiSchema.title': {
 | 
				
			||||||
      type: 'string',
 | 
					      type: 'string',
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,6 @@ export interface CollectionSetting {
 | 
				
			|||||||
   * @default false
 | 
					   * @default false
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  inherit?: boolean;
 | 
					  inherit?: boolean;
 | 
				
			||||||
  inherits?: string[];
 | 
					 | 
				
			||||||
  category?: any[];
 | 
					  category?: any[];
 | 
				
			||||||
  hidden?: boolean;
 | 
					  hidden?: boolean;
 | 
				
			||||||
  description?: string;
 | 
					  description?: string;
 | 
				
			||||||
@ -120,6 +119,23 @@ interface AclRoleSetting {
 | 
				
			|||||||
  key?: string;
 | 
					  key?: string;
 | 
				
			||||||
  //菜单权限配置
 | 
					  //菜单权限配置
 | 
				
			||||||
  menuUiSchemas?: string[];
 | 
					  menuUiSchemas?: string[];
 | 
				
			||||||
 | 
					  dataSourceKey?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface DatabaseSetting {
 | 
				
			||||||
 | 
					  database: string;
 | 
				
			||||||
 | 
					  host: string;
 | 
				
			||||||
 | 
					  port: string;
 | 
				
			||||||
 | 
					  schema?: string;
 | 
				
			||||||
 | 
					  username?: string;
 | 
				
			||||||
 | 
					  password?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					interface DataSourceSetting {
 | 
				
			||||||
 | 
					  key: string;
 | 
				
			||||||
 | 
					  displayName: string;
 | 
				
			||||||
 | 
					  type: string;
 | 
				
			||||||
 | 
					  options: DatabaseSetting;
 | 
				
			||||||
 | 
					  enabled?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PageConfig {
 | 
					export interface PageConfig {
 | 
				
			||||||
@ -229,6 +245,15 @@ interface ExtendUtils {
 | 
				
			|||||||
   * 更新角色权限配置
 | 
					   * 更新角色权限配置
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  updateRole: <T = any>(roleSetting: AclRoleSetting) => Promise<T>;
 | 
					  updateRole: <T = any>(roleSetting: AclRoleSetting) => Promise<T>;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 创建一个外部数据源(pg)
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  mockExternalDataSource: <T = any>(DataSourceSetting: DataSourceSetting) => Promise<T>;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 删除外部数据源
 | 
				
			||||||
 | 
					   * @param key 外部数据源key
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  destoryExternalDataSource: <T = any>(key: string) => Promise<T>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const PORT = process.env.APP_PORT || 20000;
 | 
					const PORT = process.env.APP_PORT || 20000;
 | 
				
			||||||
@ -437,6 +462,20 @@ const _test = base.extend<ExtendUtils>({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await use(updateRole);
 | 
					    await use(updateRole);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  mockExternalDataSource: async ({ browser }, use) => {
 | 
				
			||||||
 | 
					    const mockExternalDataSource = async (DataSourceSetting: DataSourceSetting) => {
 | 
				
			||||||
 | 
					      return createExternalDataSource(DataSourceSetting);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await use(mockExternalDataSource);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  destoryExternalDataSource: async ({ browser }, use) => {
 | 
				
			||||||
 | 
					    const destoryDataSource = async (key: string) => {
 | 
				
			||||||
 | 
					      return destoryExternalDataSource(key);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await use(destoryDataSource);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const test = Object.assign(_test, {
 | 
					export const test = Object.assign(_test, {
 | 
				
			||||||
@ -581,10 +620,6 @@ const deleteCollections = async (collectionNames: string[]) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const result = await api.post(`/api/collections:destroy?${params}`, {
 | 
					  const result = await api.post(`/api/collections:destroy?${params}`, {
 | 
				
			||||||
    headers,
 | 
					    headers,
 | 
				
			||||||
    params: {
 | 
					 | 
				
			||||||
      // 自动删除依赖于集合的对象(如视图),进而删除依赖于这些对象的所有对象
 | 
					 | 
				
			||||||
      cascade: true,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!result.ok()) {
 | 
					  if (!result.ok()) {
 | 
				
			||||||
@ -696,8 +731,12 @@ const updateRole = async (roleSetting: AclRoleSetting) => {
 | 
				
			|||||||
  const state = await api.storageState();
 | 
					  const state = await api.storageState();
 | 
				
			||||||
  const headers = getHeaders(state);
 | 
					  const headers = getHeaders(state);
 | 
				
			||||||
  const name = roleSetting.name;
 | 
					  const name = roleSetting.name;
 | 
				
			||||||
 | 
					  const dataSourceKey = roleSetting.dataSourceKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const result = await api.post(`/api/roles:update?filterByTk=${name}`, {
 | 
					  const url = !dataSourceKey
 | 
				
			||||||
 | 
					    ? `/api/roles:update?filterByTk=${name}`
 | 
				
			||||||
 | 
					    : `/api/dataSources/${dataSourceKey}/roles:update?filterByTk=${name}`;
 | 
				
			||||||
 | 
					  const result = await api.post(url, {
 | 
				
			||||||
    headers,
 | 
					    headers,
 | 
				
			||||||
    data: { ...roleSetting },
 | 
					    data: { ...roleSetting },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@ -724,7 +763,48 @@ const setDefaultRole = async (name) => {
 | 
				
			|||||||
    data: { roleName: name },
 | 
					    data: { roleName: name },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 创建外部数据源
 | 
				
			||||||
 | 
					 * @paramn
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const createExternalDataSource = async (dataSourceSetting: DataSourceSetting) => {
 | 
				
			||||||
 | 
					  const api = await request.newContext({
 | 
				
			||||||
 | 
					    storageState: process.env.PLAYWRIGHT_AUTH_FILE,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const state = await api.storageState();
 | 
				
			||||||
 | 
					  const headers = getHeaders(state);
 | 
				
			||||||
 | 
					  const result = await api.post(`/api/dataSources:create`, {
 | 
				
			||||||
 | 
					    headers,
 | 
				
			||||||
 | 
					    data: { ...dataSourceSetting },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!result.ok()) {
 | 
				
			||||||
 | 
					    throw new Error(await result.text());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const dataSourceData = (await result.json()).data;
 | 
				
			||||||
 | 
					  return dataSourceData;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 删除外部数据源
 | 
				
			||||||
 | 
					 * @paramn
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const destoryExternalDataSource = async (key) => {
 | 
				
			||||||
 | 
					  const api = await request.newContext({
 | 
				
			||||||
 | 
					    storageState: process.env.PLAYWRIGHT_AUTH_FILE,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const state = await api.storageState();
 | 
				
			||||||
 | 
					  const headers = getHeaders(state);
 | 
				
			||||||
 | 
					  const result = await api.post(`/api/dataSources:destroy?filterByTk=${key}`, {
 | 
				
			||||||
 | 
					    headers,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!result.ok()) {
 | 
				
			||||||
 | 
					    throw new Error(await result.text());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const dataSourceData = (await result.json()).data;
 | 
				
			||||||
 | 
					  return dataSourceData;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 根据 collection 的配置生成 Faker 数据
 | 
					 * 根据 collection 的配置生成 Faker 数据
 | 
				
			||||||
 * @param collectionSetting
 | 
					 * @param collectionSetting
 | 
				
			||||||
 | 
				
			|||||||
@ -97,6 +97,238 @@ test.describe('create collection', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// //预设字段
 | 
				
			||||||
 | 
					test.describe('create collection with preset fields', () => {
 | 
				
			||||||
 | 
					  test('all preset fields by default', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //默认提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      createdAt: true,
 | 
				
			||||||
 | 
					      createdBy: true,
 | 
				
			||||||
 | 
					      updatedAt: true,
 | 
				
			||||||
 | 
					      updatedBy: true,
 | 
				
			||||||
 | 
					      fields: expect.arrayContaining([
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'id',
 | 
				
			||||||
 | 
					          type: 'bigInt',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'createdAt',
 | 
				
			||||||
 | 
					          type: 'date',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'createdBy',
 | 
				
			||||||
 | 
					          type: 'belongsTo',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'updatedBy',
 | 
				
			||||||
 | 
					          type: 'belongsTo',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'updatedAt',
 | 
				
			||||||
 | 
					          type: 'date',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('id preset field', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover();
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('row', { name: 'ID Integer Primary key' }).locator('.ant-checkbox-input').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      createdAt: false,
 | 
				
			||||||
 | 
					      createdBy: false,
 | 
				
			||||||
 | 
					      updatedAt: false,
 | 
				
			||||||
 | 
					      updatedBy: false,
 | 
				
			||||||
 | 
					      fields: expect.arrayContaining([
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'id',
 | 
				
			||||||
 | 
					          type: 'bigInt',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('createdAt preset field', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover();
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('row', { name: 'Created at' }).locator('.ant-checkbox-input').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      createdAt: true,
 | 
				
			||||||
 | 
					      createdBy: false,
 | 
				
			||||||
 | 
					      updatedAt: false,
 | 
				
			||||||
 | 
					      updatedBy: false,
 | 
				
			||||||
 | 
					      fields: expect.arrayContaining([
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'createdAt',
 | 
				
			||||||
 | 
					          type: 'date',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('createdBy preset field', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover();
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('row', { name: 'created By' }).locator('.ant-checkbox-input').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      createdAt: false,
 | 
				
			||||||
 | 
					      createdBy: true,
 | 
				
			||||||
 | 
					      updatedAt: false,
 | 
				
			||||||
 | 
					      updatedBy: false,
 | 
				
			||||||
 | 
					      fields: expect.arrayContaining([
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'createdBy',
 | 
				
			||||||
 | 
					          type: 'belongsTo',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('updatedBy preset field', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover();
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('row', { name: 'Last updated by' }).locator('.ant-checkbox-input').check();
 | 
				
			||||||
 | 
					    //添加关系字段,断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      createdAt: false,
 | 
				
			||||||
 | 
					      createdBy: false,
 | 
				
			||||||
 | 
					      updatedAt: false,
 | 
				
			||||||
 | 
					      updatedBy: true,
 | 
				
			||||||
 | 
					      fields: expect.arrayContaining([
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'updatedBy',
 | 
				
			||||||
 | 
					          type: 'belongsTo',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('updatedAt preset field', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover();
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('row', { name: 'Last updated at' }).locator('.ant-checkbox-input').check();
 | 
				
			||||||
 | 
					    //添加关系字段,断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      createdAt: false,
 | 
				
			||||||
 | 
					      createdBy: false,
 | 
				
			||||||
 | 
					      updatedAt: true,
 | 
				
			||||||
 | 
					      updatedBy: false,
 | 
				
			||||||
 | 
					      fields: expect.arrayContaining([
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          name: 'updatedAt',
 | 
				
			||||||
 | 
					          type: 'date',
 | 
				
			||||||
 | 
					          // 其他属性
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('unselect preset fields', async ({ page }) => {
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Create collection down' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover();
 | 
				
			||||||
 | 
					    await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('api/collections:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      createdAt: false,
 | 
				
			||||||
 | 
					      createdBy: false,
 | 
				
			||||||
 | 
					      updatedAt: false,
 | 
				
			||||||
 | 
					      updatedBy: false,
 | 
				
			||||||
 | 
					      fields: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test.describe('configure fields', () => {
 | 
					test.describe('configure fields', () => {
 | 
				
			||||||
  test('basic', async ({ page, mockCollections }) => {
 | 
					  test('basic', async ({ page, mockCollections }) => {
 | 
				
			||||||
    test.slow();
 | 
					    test.slow();
 | 
				
			||||||
@ -261,6 +493,148 @@ test.describe('configure fields', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//创建非Id为主键/唯一索引
 | 
				
			||||||
 | 
					test.describe('create primary key  or unique index other than ID.', () => {
 | 
				
			||||||
 | 
					  test('integer field as primary key', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const name = uid();
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: name,
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(name);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'Integer' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    await page.getByLabel('Primary').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('/fields:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit-fields-').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      unique: false,
 | 
				
			||||||
 | 
					      interface: 'integer',
 | 
				
			||||||
 | 
					      primaryKey: true,
 | 
				
			||||||
 | 
					      type: 'bigInt',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('integer field set unique index', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const name = uid();
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: name,
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(name);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'Integer' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    // Primary 和 Unique选中一个,其中一个自动取消勾选
 | 
				
			||||||
 | 
					    await page.getByLabel('Primary').check();
 | 
				
			||||||
 | 
					    await page.getByLabel('Unique').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('/fields:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit-fields-').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      unique: true,
 | 
				
			||||||
 | 
					      interface: 'integer',
 | 
				
			||||||
 | 
					      primaryKey: false,
 | 
				
			||||||
 | 
					      type: 'bigInt',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('input(string) field set unique index', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const name = uid();
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: name,
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(name);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'Single line text' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    // Primary 和 Unique选中一个,其中一个自动取消勾选
 | 
				
			||||||
 | 
					    await page.getByLabel('Primary').check();
 | 
				
			||||||
 | 
					    await page.getByLabel('Unique').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('/fields:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit-fields-').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      unique: true,
 | 
				
			||||||
 | 
					      interface: 'input',
 | 
				
			||||||
 | 
					      primaryKey: false,
 | 
				
			||||||
 | 
					      type: 'string',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('input(string) field set as primary key', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const name = uid();
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: name,
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(name);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'Single line text' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid());
 | 
				
			||||||
 | 
					    // Primary 和 Unique选中一个,其中一个自动取消勾选
 | 
				
			||||||
 | 
					    await page.getByLabel('Unique').check();
 | 
				
			||||||
 | 
					    await page.getByLabel('Primary').check();
 | 
				
			||||||
 | 
					    //断言提交的data是否符合预期
 | 
				
			||||||
 | 
					    const [request] = await Promise.all([
 | 
				
			||||||
 | 
					      page.waitForRequest((request) => request.url().includes('/fields:create')),
 | 
				
			||||||
 | 
					      page.getByLabel('action-Action-Submit-fields-').click(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const postData = request.postDataJSON();
 | 
				
			||||||
 | 
					    //提交的数据符合预期
 | 
				
			||||||
 | 
					    expect(postData).toMatchObject({
 | 
				
			||||||
 | 
					      unique: false,
 | 
				
			||||||
 | 
					      interface: 'input',
 | 
				
			||||||
 | 
					      primaryKey: true,
 | 
				
			||||||
 | 
					      type: 'string',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test.describe('edit', () => {
 | 
					test.describe('edit', () => {
 | 
				
			||||||
  test('basic', async ({ page, mockCollection }) => {
 | 
					  test('basic', async ({ page, mockCollection }) => {
 | 
				
			||||||
    const collectionDisplayName = uid();
 | 
					    const collectionDisplayName = uid();
 | 
				
			||||||
@ -283,3 +657,291 @@ test.describe('edit', () => {
 | 
				
			|||||||
    await expect(page.getByRole('cell', { name: newDescription, exact: true })).toBeVisible();
 | 
					    await expect(page.getByRole('cell', { name: newDescription, exact: true })).toBeVisible();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//创建关系字段
 | 
				
			||||||
 | 
					//sourceKey, targetKey, optional field types are [string ',' bigInt ',' integer ',' uuid ',' uid '] and are non primary key field with a Unique index set
 | 
				
			||||||
 | 
					test.describe('association constraints support selecting non-primary key fields with Unique indexes', () => {
 | 
				
			||||||
 | 
					  const fields = [
 | 
				
			||||||
 | 
					    { name: 'id', interface: 'id', type: 'bigInt' },
 | 
				
			||||||
 | 
					    { name: 'string', interface: 'input', type: 'string', unique: true },
 | 
				
			||||||
 | 
					    { name: 'bigInt', type: 'bigInt', interface: 'integer', unique: true },
 | 
				
			||||||
 | 
					    { name: 'integer', type: 'integer', interface: 'input', unique: true },
 | 
				
			||||||
 | 
					    { name: 'uuid', type: 'uuid', interface: 'input', unique: true },
 | 
				
			||||||
 | 
					    { name: 'uid', type: 'uid', interface: 'input', unique: true },
 | 
				
			||||||
 | 
					    { name: 'string1', interface: 'input', type: 'string' },
 | 
				
			||||||
 | 
					    { name: 'bigInt1', type: 'bigInt', interface: 'integer' },
 | 
				
			||||||
 | 
					    { name: 'integer1', type: 'integer', interface: 'input' },
 | 
				
			||||||
 | 
					    { name: 'uuid1', type: 'uuid', interface: 'input' },
 | 
				
			||||||
 | 
					    { name: 'uid1', type: 'uid', interface: 'input' },
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					  test('oho sourceKey', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const collectionName = uid();
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: collectionName,
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      fields,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(collectionName);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'One to one (has one)' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-SourceKey-fields-').click();
 | 
				
			||||||
 | 
					    // 获取所有选项的文本内容
 | 
				
			||||||
 | 
					    const options = await page.locator('.rc-virtual-list').evaluate(() => {
 | 
				
			||||||
 | 
					      const optionElements = document.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					      return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期
 | 
				
			||||||
 | 
					    expect(options).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('obo targetKey', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const collectionName = uid();
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: collectionName,
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      fields,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(collectionName);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'One to one (belongs to)' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Select-fields-Target collection').click();
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: collectionName }).locator('div').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-TargetKey-fields-').click();
 | 
				
			||||||
 | 
					    // 获取所有选项的文本内容
 | 
				
			||||||
 | 
					    const options = await page
 | 
				
			||||||
 | 
					      .locator('.rc-virtual-list')
 | 
				
			||||||
 | 
					      .nth(1)
 | 
				
			||||||
 | 
					      .evaluate((element) => {
 | 
				
			||||||
 | 
					        const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					        return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期
 | 
				
			||||||
 | 
					    expect(options).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  test('o2m targetKey & sourceKey', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const collectionName = uid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: collectionName,
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      fields,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(collectionName);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'One to many' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-SourceKey-fields-').click();
 | 
				
			||||||
 | 
					    // sourceKey 选项符合预期
 | 
				
			||||||
 | 
					    const sourcekeyOptions = await page.locator('.rc-virtual-list').evaluate((element) => {
 | 
				
			||||||
 | 
					      const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					      return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期
 | 
				
			||||||
 | 
					    expect(sourcekeyOptions).toEqual(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Select-fields-Target collection').click();
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: collectionName }).locator('div').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-TargetKey-fields-').click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //targetKey 选项符合预期
 | 
				
			||||||
 | 
					    const targetKeyOptions = await page
 | 
				
			||||||
 | 
					      .locator('.rc-virtual-list')
 | 
				
			||||||
 | 
					      .nth(2)
 | 
				
			||||||
 | 
					      .evaluate((element) => {
 | 
				
			||||||
 | 
					        const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					        return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期,o2m的targetkey 不限制unique
 | 
				
			||||||
 | 
					    expect(targetKeyOptions).toEqual(
 | 
				
			||||||
 | 
					      expect.arrayContaining([
 | 
				
			||||||
 | 
					        'ID',
 | 
				
			||||||
 | 
					        'string',
 | 
				
			||||||
 | 
					        'bigInt',
 | 
				
			||||||
 | 
					        'integer',
 | 
				
			||||||
 | 
					        'uuid',
 | 
				
			||||||
 | 
					        'uid',
 | 
				
			||||||
 | 
					        'string1',
 | 
				
			||||||
 | 
					        'bigInt1',
 | 
				
			||||||
 | 
					        'integer1',
 | 
				
			||||||
 | 
					        'uuid1',
 | 
				
			||||||
 | 
					        'uid1',
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('m2o targetKey & foreignKey', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const collectionName = uid();
 | 
				
			||||||
 | 
					    const foreignKey = `f_${uid()}`;
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: collectionName,
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      fields,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(collectionName);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'Many to one' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ForeignKey-fields-').locator('input').click();
 | 
				
			||||||
 | 
					    // ForeignKey 选项符合预期
 | 
				
			||||||
 | 
					    const foreignKeyOptions = await page.locator('.rc-virtual-list').evaluate((element) => {
 | 
				
			||||||
 | 
					      const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					      return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期
 | 
				
			||||||
 | 
					    expect(foreignKeyOptions).toEqual(
 | 
				
			||||||
 | 
					      expect.arrayContaining([
 | 
				
			||||||
 | 
					        'ID',
 | 
				
			||||||
 | 
					        'string',
 | 
				
			||||||
 | 
					        'bigInt',
 | 
				
			||||||
 | 
					        'integer',
 | 
				
			||||||
 | 
					        'uuid',
 | 
				
			||||||
 | 
					        'uid',
 | 
				
			||||||
 | 
					        'string1',
 | 
				
			||||||
 | 
					        'bigInt1',
 | 
				
			||||||
 | 
					        'integer1',
 | 
				
			||||||
 | 
					        'uuid1',
 | 
				
			||||||
 | 
					        'uid1',
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    //ForeignKey 支持自定义输入和选择
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ForeignKey-fields-').locator('input').clear();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ForeignKey-fields-').locator('input').fill(foreignKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inputValue = await page.getByLabel('block-item-ForeignKey-fields-').locator('input').inputValue();
 | 
				
			||||||
 | 
					    expect(inputValue).toEqual(foreignKey);
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: 'uuid1' }).locator('div').click();
 | 
				
			||||||
 | 
					    const optionValue = await page.getByLabel('block-item-ForeignKey-fields-').locator('input').inputValue();
 | 
				
			||||||
 | 
					    expect(optionValue).toEqual('uuid1');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Select-fields-Target collection').click();
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: collectionName }).locator('div').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-TargetKey-fields-').click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //targetKey 选项符合预期
 | 
				
			||||||
 | 
					    const targetKeyOptions = await page
 | 
				
			||||||
 | 
					      .locator('.rc-virtual-list')
 | 
				
			||||||
 | 
					      .nth(2)
 | 
				
			||||||
 | 
					      .evaluate((element) => {
 | 
				
			||||||
 | 
					        const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					        return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期, 限制unique
 | 
				
			||||||
 | 
					    expect(targetKeyOptions).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('m2m sourceKey & foreignKey & targetKey', async ({ page, mockCollection }) => {
 | 
				
			||||||
 | 
					    const collectionName = uid();
 | 
				
			||||||
 | 
					    const foreignKey = `f_${uid()}`;
 | 
				
			||||||
 | 
					    await mockCollection({
 | 
				
			||||||
 | 
					      name: collectionName,
 | 
				
			||||||
 | 
					      autoGenId: true,
 | 
				
			||||||
 | 
					      fields,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await page.goto('/admin/settings/data-source-manager/list');
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Configure' }).first().click();
 | 
				
			||||||
 | 
					    await page.getByLabel('action-Filter.Action-Filter-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).click();
 | 
				
			||||||
 | 
					    await page.getByRole('textbox').nth(1).fill(collectionName);
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'Submit' }).click();
 | 
				
			||||||
 | 
					    await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click();
 | 
				
			||||||
 | 
					    await page.getByRole('button', { name: 'plus Add field' }).click();
 | 
				
			||||||
 | 
					    await page.getByRole('menuitem', { name: 'Many to many' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-SourceKey-fields-').click();
 | 
				
			||||||
 | 
					    // sourceKey 选项符合预期
 | 
				
			||||||
 | 
					    const sourcekeyOptions = await page.locator('.rc-virtual-list').evaluate((element) => {
 | 
				
			||||||
 | 
					      const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					      return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(sourcekeyOptions).toEqual(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ForeignKey1 选项符合预期
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ThroughCollection-').click();
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: collectionName }).locator('div').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ForeignKey-fields-Foreign key 1').locator('input').click();
 | 
				
			||||||
 | 
					    const foreignKeyOptions = await page
 | 
				
			||||||
 | 
					      .locator('.rc-virtual-list')
 | 
				
			||||||
 | 
					      .nth(2)
 | 
				
			||||||
 | 
					      .evaluate((element) => {
 | 
				
			||||||
 | 
					        const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					        return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期
 | 
				
			||||||
 | 
					    expect(foreignKeyOptions).toEqual(
 | 
				
			||||||
 | 
					      expect.arrayContaining([
 | 
				
			||||||
 | 
					        'ID',
 | 
				
			||||||
 | 
					        'string',
 | 
				
			||||||
 | 
					        'bigInt',
 | 
				
			||||||
 | 
					        'integer',
 | 
				
			||||||
 | 
					        'uuid',
 | 
				
			||||||
 | 
					        'uid',
 | 
				
			||||||
 | 
					        'string1',
 | 
				
			||||||
 | 
					        'bigInt1',
 | 
				
			||||||
 | 
					        'integer1',
 | 
				
			||||||
 | 
					        'uuid1',
 | 
				
			||||||
 | 
					        'uid1',
 | 
				
			||||||
 | 
					      ]),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    //ForeignKey1 支持自定义输入和选择
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ForeignKey-fields-Foreign key 1').locator('input').clear();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-ForeignKey-fields-Foreign key 1').locator('input').fill(foreignKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inputValue = await page
 | 
				
			||||||
 | 
					      .getByLabel('block-item-ForeignKey-fields-Foreign key 1')
 | 
				
			||||||
 | 
					      .locator('input')
 | 
				
			||||||
 | 
					      .inputValue();
 | 
				
			||||||
 | 
					    expect(inputValue).toEqual(foreignKey);
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: 'uuid1' }).locator('div').click();
 | 
				
			||||||
 | 
					    const optionValue = await page
 | 
				
			||||||
 | 
					      .getByLabel('block-item-ForeignKey-fields-Foreign key 1')
 | 
				
			||||||
 | 
					      .locator('input')
 | 
				
			||||||
 | 
					      .inputValue();
 | 
				
			||||||
 | 
					    expect(optionValue).toEqual('uuid1');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-Select-fields-Target collection').click();
 | 
				
			||||||
 | 
					    await page.getByRole('option', { name: collectionName }).locator('div').click();
 | 
				
			||||||
 | 
					    await page.getByLabel('block-item-TargetKey-fields-').click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //targetKey 选项符合预期
 | 
				
			||||||
 | 
					    const targetKeyOptions = await page
 | 
				
			||||||
 | 
					      .locator('.rc-virtual-list')
 | 
				
			||||||
 | 
					      .nth(2)
 | 
				
			||||||
 | 
					      .evaluate((element) => {
 | 
				
			||||||
 | 
					        const optionElements = element.querySelectorAll('.ant-select-item-option');
 | 
				
			||||||
 | 
					        return Array.from(optionElements).map((option) => option.textContent);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 断言下拉列表是否符合预期, 限制unique
 | 
				
			||||||
 | 
					    expect(targetKeyOptions).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,9 @@ export const CollectionFieldInterfaceSelect = observer(
 | 
				
			|||||||
      </Tag>
 | 
					      </Tag>
 | 
				
			||||||
    ) : (
 | 
					    ) : (
 | 
				
			||||||
      <Select
 | 
					      <Select
 | 
				
			||||||
 | 
					        aria-label={`field-interface-${record?.type}`}
 | 
				
			||||||
 | 
					        //@ts-ignore
 | 
				
			||||||
 | 
					        role="button"
 | 
				
			||||||
        defaultValue={value}
 | 
					        defaultValue={value}
 | 
				
			||||||
        style={{ width: '100%' }}
 | 
					        style={{ width: '100%' }}
 | 
				
			||||||
        popupMatchSelectWidth={false}
 | 
					        popupMatchSelectWidth={false}
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,9 @@ export const FieldTitleInput = observer(
 | 
				
			|||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
      setTitleValue(value);
 | 
					      setTitleValue(value);
 | 
				
			||||||
    }, [value]);
 | 
					    }, [value]);
 | 
				
			||||||
    return <Input value={titleValue} onChange={handleChange} style={{ minWidth: '100px' }} />;
 | 
					    return (
 | 
				
			||||||
 | 
					      <Input value={titleValue} onChange={handleChange} style={{ minWidth: '100px' }} aria-label="field-title-input" />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  { displayName: 'FieldTitleInput' },
 | 
					  { displayName: 'FieldTitleInput' },
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,9 @@ export const FieldType = observer(
 | 
				
			|||||||
      <Tag>{value}</Tag>
 | 
					      <Tag>{value}</Tag>
 | 
				
			||||||
    ) : (
 | 
					    ) : (
 | 
				
			||||||
      <Select
 | 
					      <Select
 | 
				
			||||||
 | 
					        aria-label={`field-type-${value}`}
 | 
				
			||||||
 | 
					        //@ts-ignore
 | 
				
			||||||
 | 
					        role="button"
 | 
				
			||||||
        defaultValue={value}
 | 
					        defaultValue={value}
 | 
				
			||||||
        popupMatchSelectWidth={false}
 | 
					        popupMatchSelectWidth={false}
 | 
				
			||||||
        style={{ width: '100%' }}
 | 
					        style={{ width: '100%' }}
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,8 @@ export const ViewDatabaseConnectionAction = () => {
 | 
				
			|||||||
        onClick={() => {
 | 
					        onClick={() => {
 | 
				
			||||||
          navigate(getConnectionCollectionPath(record.key));
 | 
					          navigate(getConnectionCollectionPath(record.key));
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
 | 
					        role="button"
 | 
				
			||||||
 | 
					        aria-label={`${record?.key}-Configure`}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {t('Configure')}
 | 
					        {t('Configure')}
 | 
				
			||||||
      </Button>
 | 
					      </Button>
 | 
				
			||||||
 | 
				
			|||||||
@ -125,6 +125,33 @@ describe('data source with acl', () => {
 | 
				
			|||||||
    expect(postRes.status).toBe(200);
 | 
					    expect(postRes.status).toBe(200);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should update roles resources', async () => {
 | 
				
			||||||
 | 
					    const adminUser = await app.db.getRepository('users').create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        roles: ['root'],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const adminAgent: any = app.agent().login(adminUser);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await adminAgent.resource('dataSources.roles', 'mockInstance1').update({
 | 
				
			||||||
 | 
					      filterByTk: 'member',
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        resources: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            posts: {
 | 
				
			||||||
 | 
					              actions: {
 | 
				
			||||||
 | 
					                view: {
 | 
				
			||||||
 | 
					                  fields: ['title'],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should set main data source strategy', async () => {
 | 
					  it('should set main data source strategy', async () => {
 | 
				
			||||||
    const adminUser = await app.db.getRepository('users').create({
 | 
					    const adminUser = await app.db.getRepository('users').create({
 | 
				
			||||||
      values: {
 | 
					      values: {
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,25 @@ export default {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (params.values.resources) {
 | 
				
			||||||
 | 
					        await ctx.db.getRepository('dataSourcesRolesResources').destroy({
 | 
				
			||||||
 | 
					          filter: {
 | 
				
			||||||
 | 
					            roleName: name,
 | 
				
			||||||
 | 
					            dataSourceKey,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const resource of params.values.resources) {
 | 
				
			||||||
 | 
					          await ctx.db.getRepository('dataSourcesRolesResources').create({
 | 
				
			||||||
 | 
					            values: {
 | 
				
			||||||
 | 
					              ...resource,
 | 
				
			||||||
 | 
					              roleName: name,
 | 
				
			||||||
 | 
					              dataSourceKey,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      ctx.body = connectionRoleRecord.toJSON();
 | 
					      ctx.body = connectionRoleRecord.toJSON();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await next();
 | 
					      await next();
 | 
				
			||||||
 | 
				
			|||||||
@ -13,5 +13,8 @@
 | 
				
			|||||||
    "@nocobase/test": "0.x",
 | 
					    "@nocobase/test": "0.x",
 | 
				
			||||||
    "@nocobase/utils": "0.x"
 | 
					    "@nocobase/utils": "0.x"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "pg": "^8.11.3"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
 | 
					  "gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,175 @@
 | 
				
			|||||||
 | 
					CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 users 表
 | 
				
			||||||
 | 
					CREATE TABLE users (
 | 
				
			||||||
 | 
					                     user_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					                     username VARCHAR(100) NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 profiles 表
 | 
				
			||||||
 | 
					CREATE TABLE profiles (
 | 
				
			||||||
 | 
					                        profile_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					                        address VARCHAR(255),
 | 
				
			||||||
 | 
					                        user_uuid UUID REFERENCES users(user_uuid)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 roles 表
 | 
				
			||||||
 | 
					CREATE TABLE roles (
 | 
				
			||||||
 | 
					                     role_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					                     role_name VARCHAR(100) NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 user_roles 关联表
 | 
				
			||||||
 | 
					CREATE TABLE user_roles (
 | 
				
			||||||
 | 
					                          user_uuid UUID REFERENCES users(user_uuid),
 | 
				
			||||||
 | 
					                          role_uuid UUID REFERENCES roles(role_uuid),
 | 
				
			||||||
 | 
					                          PRIMARY KEY (user_uuid, role_uuid)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 orders 表
 | 
				
			||||||
 | 
					CREATE TABLE orders (
 | 
				
			||||||
 | 
					                      order_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					                      user_uuid UUID REFERENCES users(user_uuid),
 | 
				
			||||||
 | 
					                      order_name VARCHAR(255) NOT NULL
 | 
				
			||||||
 | 
					  -- ... 其他字段
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 users 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO users (username) VALUES
 | 
				
			||||||
 | 
					                               ('Alice'),
 | 
				
			||||||
 | 
					                               ('Bob');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					INSERT INTO orders (user_uuid, order_name) VALUES
 | 
				
			||||||
 | 
					                                             ((SELECT user_uuid FROM users WHERE username = 'Alice'), 'Order1'),
 | 
				
			||||||
 | 
					                                             ((SELECT user_uuid FROM users WHERE username = 'Bob'), 'Order2');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 profiles 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO profiles (address, user_uuid) VALUES
 | 
				
			||||||
 | 
					                                            ('123 Main St', (SELECT user_uuid FROM users WHERE username = 'Alice')),
 | 
				
			||||||
 | 
					                                            ('456 High St', (SELECT user_uuid FROM users WHERE username = 'Bob'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 roles 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO roles (role_name) VALUES
 | 
				
			||||||
 | 
					                                ('Admin'),
 | 
				
			||||||
 | 
					                                ('User');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 建立 users 和 roles 之间的关系
 | 
				
			||||||
 | 
					INSERT INTO user_roles (user_uuid, role_uuid) VALUES
 | 
				
			||||||
 | 
					                                                ((SELECT user_uuid FROM users WHERE username = 'Alice'), (SELECT role_uuid FROM roles WHERE role_name = 'Admin')),
 | 
				
			||||||
 | 
					                                                ((SELECT user_uuid FROM users WHERE username = 'Bob'), (SELECT role_uuid FROM roles WHERE role_name = 'User'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE "test_table" (
 | 
				
			||||||
 | 
					                                "id2" SERIAL PRIMARY KEY,
 | 
				
			||||||
 | 
					                                "smallint" int2,
 | 
				
			||||||
 | 
					                                "integer" int4,
 | 
				
			||||||
 | 
					                                "bigint" int8,
 | 
				
			||||||
 | 
					                                "boolean" bool,
 | 
				
			||||||
 | 
					                                "numeric" numeric,
 | 
				
			||||||
 | 
					                                "real" float4,
 | 
				
			||||||
 | 
					                                "double" float8,
 | 
				
			||||||
 | 
					                                "money" money,
 | 
				
			||||||
 | 
					                                "date" date,
 | 
				
			||||||
 | 
					                                "time" time,
 | 
				
			||||||
 | 
					                                "timestamp" timestamp,
 | 
				
			||||||
 | 
					                                "timestamptz" timestamptz,
 | 
				
			||||||
 | 
					                                "interval" interval,
 | 
				
			||||||
 | 
					                                "char" bpchar,
 | 
				
			||||||
 | 
					                                "varchar" varchar,
 | 
				
			||||||
 | 
					                                "text" text,
 | 
				
			||||||
 | 
					                                "tsquery" tsquery,
 | 
				
			||||||
 | 
					                                "tsvector" tsvector,
 | 
				
			||||||
 | 
					                                "uuid" uuid,
 | 
				
			||||||
 | 
					                                "xml" xml,
 | 
				
			||||||
 | 
					                                "json" json,
 | 
				
			||||||
 | 
					                                "jsonb" jsonb,
 | 
				
			||||||
 | 
					                                "bit" bit,
 | 
				
			||||||
 | 
					                                "bitvarying" varbit,
 | 
				
			||||||
 | 
					                                "bytea" bytea,
 | 
				
			||||||
 | 
					                                "cidr" cidr,
 | 
				
			||||||
 | 
					                                "inet" inet,
 | 
				
			||||||
 | 
					                                "macaddr" macaddr,
 | 
				
			||||||
 | 
					                                "txidsnapshot" txid_snapshot,
 | 
				
			||||||
 | 
					                                "box" box,
 | 
				
			||||||
 | 
					                                "circle" circle,
 | 
				
			||||||
 | 
					                                "line" line,
 | 
				
			||||||
 | 
					                                "lseg" lseg,
 | 
				
			||||||
 | 
					                                "path" path,
 | 
				
			||||||
 | 
					                                "point" point,
 | 
				
			||||||
 | 
					                                "polygon" polygon
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 authors 表
 | 
				
			||||||
 | 
					CREATE TABLE authors (
 | 
				
			||||||
 | 
					    author_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					    author_name VARCHAR(100) NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 posts 表
 | 
				
			||||||
 | 
					CREATE TABLE posts (
 | 
				
			||||||
 | 
					    post_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					    post_title VARCHAR(100) NOT NULL,
 | 
				
			||||||
 | 
					    post_content TEXT NOT NULL,
 | 
				
			||||||
 | 
					    author_id UUID REFERENCES authors(author_id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 comments 表
 | 
				
			||||||
 | 
					CREATE TABLE comments (
 | 
				
			||||||
 | 
					    comment_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					    comment_content TEXT NOT NULL,
 | 
				
			||||||
 | 
					    post_id UUID REFERENCES posts(post_id),
 | 
				
			||||||
 | 
					    author_id UUID REFERENCES authors(author_id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 tags 表
 | 
				
			||||||
 | 
					CREATE TABLE tags (
 | 
				
			||||||
 | 
					    tag_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
 | 
				
			||||||
 | 
					    tag_name VARCHAR(100) NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建 post_tags 关联表
 | 
				
			||||||
 | 
					CREATE TABLE post_tags (
 | 
				
			||||||
 | 
					    post_id UUID REFERENCES posts(post_id),
 | 
				
			||||||
 | 
					    tag_id UUID REFERENCES tags(tag_id),
 | 
				
			||||||
 | 
					    PRIMARY KEY (post_id, tag_id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 authors 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO authors (author_name) VALUES
 | 
				
			||||||
 | 
					    ('Author1'),
 | 
				
			||||||
 | 
					    ('Author2');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 posts 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO posts (post_title, post_content, author_id) VALUES
 | 
				
			||||||
 | 
					    ('Post1', 'This is the content of Post1', (SELECT author_id FROM authors WHERE author_name = 'Author1')),
 | 
				
			||||||
 | 
					    ('Post2', 'This is the content of Post2', (SELECT author_id FROM authors WHERE author_name = 'Author2'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 comments 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO comments (comment_content, post_id, author_id) VALUES
 | 
				
			||||||
 | 
					    ('This is a comment on Post1', (SELECT post_id FROM posts WHERE post_title = 'Post1'), (SELECT author_id FROM authors WHERE author_name = 'Author1')),
 | 
				
			||||||
 | 
					    ('This is a comment on Post2', (SELECT post_id FROM posts WHERE post_title = 'Post2'), (SELECT author_id FROM authors WHERE author_name = 'Author2'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 tags 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO tags (tag_name) VALUES
 | 
				
			||||||
 | 
					    ('Tag1'),
 | 
				
			||||||
 | 
					    ('Tag2');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 建立 posts 和 tags 之间的关系
 | 
				
			||||||
 | 
					INSERT INTO post_tags (post_id, tag_id) VALUES
 | 
				
			||||||
 | 
					    ((SELECT post_id FROM posts WHERE post_title = 'Post1'), (SELECT tag_id FROM tags WHERE tag_name = 'Tag1')),
 | 
				
			||||||
 | 
					    ((SELECT post_id FROM posts WHERE post_title = 'Post2'), (SELECT tag_id FROM tags WHERE tag_name = 'Tag2'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 创建无主键的表 test_table_no_pk
 | 
				
			||||||
 | 
					CREATE TABLE test_table_no_pk (
 | 
				
			||||||
 | 
					    column1 VARCHAR(100),
 | 
				
			||||||
 | 
					    column2 INT
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 向 test_table_no_pk 表插入测试数据
 | 
				
			||||||
 | 
					INSERT INTO test_table_no_pk (column1, column2) VALUES
 | 
				
			||||||
 | 
					    ('TestData1', 1),
 | 
				
			||||||
 | 
					    ('TestData2', 2);
 | 
				
			||||||
@ -1,11 +1,14 @@
 | 
				
			|||||||
import Database, { Collection, Repository } from '@nocobase/database';
 | 
					import Database, { Collection, Repository } from '@nocobase/database';
 | 
				
			||||||
import { CollectionRepository } from '@nocobase/plugin-collection-manager';
 | 
					import { CollectionRepository } from '@nocobase/plugin-collection-manager';
 | 
				
			||||||
import { Plugin } from '@nocobase/server';
 | 
					import { InstallOptions, Plugin } from '@nocobase/server';
 | 
				
			||||||
import { merge, uid } from '@nocobase/utils';
 | 
					import { merge, uid } from '@nocobase/utils';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import collectionTemplates from './collection-templates';
 | 
					import collectionTemplates from './collection-templates';
 | 
				
			||||||
import * as fieldInterfaces from './field-interfaces';
 | 
					import * as fieldInterfaces from './field-interfaces';
 | 
				
			||||||
import { mockAttachment } from './field-interfaces';
 | 
					import { mockAttachment } from './field-interfaces';
 | 
				
			||||||
 | 
					import { Client } from 'pg';
 | 
				
			||||||
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					import { promises as fs } from 'fs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PluginMockCollectionsServer extends Plugin {
 | 
					export class PluginMockCollectionsServer extends Plugin {
 | 
				
			||||||
  async load() {
 | 
					  async load() {
 | 
				
			||||||
@ -304,6 +307,41 @@ export class PluginMockCollectionsServer extends Plugin {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async install(options?: InstallOptions) {
 | 
				
			||||||
 | 
					    const dbName = this.app.db.options.database;
 | 
				
			||||||
 | 
					    const externalDB = `${dbName}_external_test`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create external database
 | 
				
			||||||
 | 
					    let client = new Client({
 | 
				
			||||||
 | 
					      user: process.env.DB_USER,
 | 
				
			||||||
 | 
					      password: process.env.DB_PASSWORD,
 | 
				
			||||||
 | 
					      host: process.env.DB_HOST,
 | 
				
			||||||
 | 
					      database: process.env.DB_DATABASE,
 | 
				
			||||||
 | 
					      port: parseInt(process.env.DB_PORT),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await client.connect();
 | 
				
			||||||
 | 
					    await client.query(`DROP DATABASE IF EXISTS ${externalDB}`);
 | 
				
			||||||
 | 
					    await client.query(`CREATE DATABASE ${externalDB}`);
 | 
				
			||||||
 | 
					    await client.end();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // import sql import external database
 | 
				
			||||||
 | 
					    client = new Client({
 | 
				
			||||||
 | 
					      user: process.env.DB_USER,
 | 
				
			||||||
 | 
					      password: process.env.DB_PASSWORD,
 | 
				
			||||||
 | 
					      host: process.env.DB_HOST,
 | 
				
			||||||
 | 
					      database: externalDB,
 | 
				
			||||||
 | 
					      port: parseInt(process.env.DB_PORT),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await client.connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sqlFile = path.resolve(__dirname, './external.sql');
 | 
				
			||||||
 | 
					    const sql: string = await fs.readFile(sqlFile, 'utf8');
 | 
				
			||||||
 | 
					    await client.query(sql);
 | 
				
			||||||
 | 
					    await client.end();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default PluginMockCollectionsServer;
 | 
					export default PluginMockCollectionsServer;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user