lobehub/.agents/skills/testing/references/db-model-test.md
Innei fcdaf9d814 🔧 chore: update eslint v2 configuration and suppressions (#12133)
* v2 init

* chore: update eslint suppressions and package dependencies

- Removed several eslint suppressions related to array sorting and reversing from eslint-suppressions.json to clean up the configuration.
- Updated @lobehub/lint package version from 2.0.0-beta.6 to 2.0.0-beta.7 in package.json for improvements and bug fixes.
- Made minor formatting adjustments in vitest.config.mts and various SKILL.md files for better readability and consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* fix: clean up import statements and formatting

- Removed unnecessary whitespace in replaceComponentImports.ts for improved readability.
- Standardized import statements in contextEngineering.ts and createAgentExecutors.ts by adding missing spaces for consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update eslint suppressions and clean up code formatting

* 🐛 fix: use vi.hoisted for mock variable initialization

Fix TDZ error in persona service test by using vi.hoisted() to ensure
mock variables are available when vi.mock factory runs.

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-02-11 13:04:48 +08:00

3.3 KiB

Database Model Testing Guide

Test packages/database Model layer.

Dual Environment Verification (Required)

# 1. Client environment (fast)
cd packages/database && TEST_SERVER_DB=0 bunx vitest run --silent='passed-only' '[file]'

# 2. Server environment (compatibility)
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'

User Permission Check - Security First 🔒

Critical security requirement: All user data operations must include permission checks.

// ❌ DANGEROUS: Missing permission check
update = async (id: string, data: Partial<MyModel>) => {
  return this.db
    .update(myTable)
    .set(data)
    .where(eq(myTable.id, id)) // Only checks ID
    .returning();
};

// ✅ SECURE: Permission check included
update = async (id: string, data: Partial<MyModel>) => {
  return this.db
    .update(myTable)
    .set(data)
    .where(
      and(
        eq(myTable.id, id),
        eq(myTable.userId, this.userId), // ✅ Permission check
      ),
    )
    .returning();
};

Test File Structure

// @vitest-environment node
describe('MyModel', () => {
  describe('create', () => {
    /* ... */
  });
  describe('queryAll', () => {
    /* ... */
  });
  describe('update', () => {
    it('should update own records');
    it('should NOT update other users records'); // 🔒 Security
  });
  describe('delete', () => {
    it('should delete own records');
    it('should NOT delete other users records'); // 🔒 Security
  });
  describe('user isolation', () => {
    it('should enforce user data isolation'); // 🔒 Core security
  });
});

Security Test Example

it('should not update records of other users', async () => {
  const [otherUserRecord] = await serverDB
    .insert(myTable)
    .values({ userId: 'other-user', data: 'original' })
    .returning();

  const result = await myModel.update(otherUserRecord.id, { data: 'hacked' });

  expect(result).toBeUndefined();
  const unchanged = await serverDB.query.myTable.findFirst({
    where: eq(myTable.id, otherUserRecord.id),
  });
  expect(unchanged?.data).toBe('original');
});

Data Management

const userId = 'test-user';
const otherUserId = 'other-user';

beforeEach(async () => {
  await serverDB.delete(users);
  await serverDB.insert(users).values([{ id: userId }, { id: otherUserId }]);
});

afterEach(async () => {
  await serverDB.delete(users);
});

Foreign Key Handling

// ❌ Wrong: Invalid foreign key
const testData = { asyncTaskId: 'invalid-uuid', fileId: 'non-existent' };

// ✅ Correct: Use null
const testData = { asyncTaskId: null, fileId: null };

// ✅ Or: Create referenced record first
beforeEach(async () => {
  const [asyncTask] = await serverDB
    .insert(asyncTasks)
    .values({ id: 'valid-id', status: 'pending' })
    .returning();
  testData.asyncTaskId = asyncTask.id;
});

Predictable Sorting

// ✅ Use explicit timestamps
const oldDate = new Date('2024-01-01T10:00:00Z');
const newDate = new Date('2024-01-02T10:00:00Z');
await serverDB.insert(table).values([
  { ...data1, createdAt: oldDate },
  { ...data2, createdAt: newDate },
]);

// ❌ Don't rely on insert order
await serverDB.insert(table).values([data1, data2]); // Unpredictable