fix tests

This commit is contained in:
Sean Sube 2025-06-14 19:22:52 -05:00
parent c6a647202b
commit cca6f4176f
No known key found for this signature in database
GPG Key ID: 3EED7B957D362AF1
12 changed files with 206 additions and 398 deletions

View File

@ -1,5 +0,0 @@
module.exports = {
rules: {
'no-non-null-assertion': 'error',
},
};

View File

@ -21,27 +21,31 @@ A task management system with receipt printer integration, designed to help peop
## Installation ## Installation
1. Clone the repository: 1. Clone the repository:
```bash ```bash
git clone https://github.com/yourusername/task-receipts.git git clone https://github.com/yourusername/task-receipts.git
cd task-receipts cd task-receipts
``` ```
2. Install dependencies: 2. Install dependencies:
```bash ```bash
npm install npm install
``` ```
3. Set up the database: 3. Set up the database:
```bash ```bash
npx knex migrate:latest npx knex migrate:latest
``` ```
4. Start the development server: 4. Start the development server:
```bash ```bash
npm run dev npm run dev
``` ```
The server will start at http://localhost:3000, and the GraphQL endpoint will be available at http://localhost:3000/graphql. The server will start at <http://localhost:3000>, and the GraphQL endpoint will be available at <http://localhost:3000/graphql>.
## Development ## Development

8
docs/rules.md Normal file
View File

@ -0,0 +1,8 @@
# Testing and Code Quality Rules
- **No non-null assertions**: Non-null assertions (the `!` operator) are a hack for missing null checks or extraneous null types. Always use proper null checks and handle possible null/undefined values explicitly.
- **Do not throw errors within tests**: Use proper test assertions (e.g., `expect(value).not.toBeNull()`) instead of throwing errors from within tests. This ensures test failures are reported clearly and consistently.
- **Do not use `any`**: Use proper types instead of `any`.
- **Do not use `jest.fn()`**: Use proper test assertions (e.g., `expect(value).not.toBeNull()`) instead of throwing errors from within tests. This ensures test failures are reported clearly and consistently.
- **Use the in-memory database for tests**: Use the in-memory database for tests. This is a good way to test the code without having to set up a real database.
- **Always use repositories for database operations**: Use repositories for database operations. This is a good way to test the code without having to set up a real database.

View File

@ -1,4 +0,0 @@
# Testing and Code Quality Rules
- **No non-null assertions**: Non-null assertions (the `!` operator) are a hack for missing null checks or extraneous null types. Always use proper null checks and handle possible null/undefined values explicitly.
- **Do not throw errors within tests**: Use proper test assertions (e.g., `expect(value).not.toBeNull()`) instead of throwing errors from within tests. This ensures test failures are reported clearly and consistently.

30
server/.eslintrc.js Normal file
View File

@ -0,0 +1,30 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
plugins: ['@typescript-eslint'],
env: {
node: true,
es6: true,
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'no-unused-vars': 'off',
'no-throw-literal': 'error',
'semi': ['error', 'always'],
'quotes': ['error', 'single'],
'indent': ['error', 2],
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json',
},
};

View File

@ -37,7 +37,7 @@ describe('Database Operations', () => {
name: 'Parent Group', name: 'Parent Group',
}; };
const [parentId] = await groups(testDb).insert(parentGroupData); const [_parentId] = await groups(testDb).insert(parentGroupData);
const childGroupData: Omit<Group, 'id' | 'created_at' | 'updated_at'> = { const childGroupData: Omit<Group, 'id' | 'created_at' | 'updated_at'> = {
name: 'Child Group', name: 'Child Group',

View File

@ -1,10 +1,11 @@
import { InMemoryRepository, InMemoryPrintHistoryRepository, InMemoryStepRepository } from '../in-memory-repository'; import { InMemoryRepository, InMemoryPrintHistoryRepository, InMemoryStepRepository } from '../in-memory-repository';
import { testDb } from '../../__tests__/setup';
describe('InMemoryRepository', () => { describe('InMemoryRepository', () => {
let repository: InMemoryRepository<{ id: number; name: string }>; let repository: InMemoryRepository<{ id: number; name: string }>;
beforeEach(() => { beforeEach(() => {
repository = new InMemoryRepository(); repository = new InMemoryRepository(testDb, 'test_table');
}); });
it('should create and find items', async () => { it('should create and find items', async () => {
@ -64,7 +65,7 @@ describe('InMemoryPrintHistoryRepository', () => {
let repository: InMemoryPrintHistoryRepository; let repository: InMemoryPrintHistoryRepository;
beforeEach(() => { beforeEach(() => {
repository = new InMemoryPrintHistoryRepository(); repository = new InMemoryPrintHistoryRepository(testDb);
}); });
it('should find print history by task id', async () => { it('should find print history by task id', async () => {
@ -94,7 +95,7 @@ describe('InMemoryStepRepository', () => {
let repository: InMemoryStepRepository; let repository: InMemoryStepRepository;
beforeEach(() => { beforeEach(() => {
repository = new InMemoryStepRepository(); repository = new InMemoryStepRepository(testDb);
}); });
it('should find steps by task id', async () => { it('should find steps by task id', async () => {

View File

@ -1,9 +1,15 @@
import { PrintHistory, Step } from '@shared/index'; import { PrintHistory, Step } from '@shared/index';
import { Knex } from 'knex';
import { BaseRepository } from './base-repository';
export class InMemoryRepository<T extends { id: number }> { export class InMemoryRepository<T extends { id: number; created_at?: Date; updated_at?: Date }> extends BaseRepository<T> {
private items: T[] = []; private items: T[] = [];
private nextId = 1; private nextId = 1;
constructor(db: Knex, tableName: string) {
super(db, tableName);
}
async findAll(): Promise<T[]> { async findAll(): Promise<T[]> {
return [...this.items]; return [...this.items];
} }
@ -40,18 +46,28 @@ export class InMemoryRepository<T extends { id: number }> {
} }
export class InMemoryPrintHistoryRepository extends InMemoryRepository<PrintHistory> { export class InMemoryPrintHistoryRepository extends InMemoryRepository<PrintHistory> {
constructor(db: Knex) {
super(db, 'print_history');
}
async findByTaskId(taskId: number): Promise<PrintHistory[]> { async findByTaskId(taskId: number): Promise<PrintHistory[]> {
return (await this.findAll()).filter(ph => ph.task_id === taskId); return (await this.findAll()).filter(ph => ph.task_id === taskId);
} }
async findByStepId(stepId: number): Promise<PrintHistory[]> { async findByStepId(stepId: number): Promise<PrintHistory[]> {
return (await this.findAll()).filter(ph => ph.step_id === stepId); return (await this.findAll()).filter(ph => ph.step_id === stepId);
} }
} }
export class InMemoryStepRepository extends InMemoryRepository<Step> { export class InMemoryStepRepository extends InMemoryRepository<Step> {
constructor(db: Knex) {
super(db, 'steps');
}
async findByTaskId(taskId: number): Promise<Step[]> { async findByTaskId(taskId: number): Promise<Step[]> {
return (await this.findAll()).filter(step => step.task_id === taskId); return (await this.findAll()).filter(step => step.task_id === taskId);
} }
async incrementPrintCount(id: number): Promise<boolean> { async incrementPrintCount(id: number): Promise<boolean> {
const step = await this.findById(id); const step = await this.findById(id);
if (!step) return false; if (!step) return false;

View File

@ -2,7 +2,7 @@ import { jest } from '@jest/globals';
import { testDb } from '../../db/__tests__/setup'; import { testDb } from '../../db/__tests__/setup';
import { resolvers } from '../resolvers'; import { resolvers } from '../resolvers';
import { GroupRepository, TaskRepository, StepRepository, UserRepository, NoteRepository, PrintHistoryRepository, ImageRepository } from '../../db/repositories'; import { GroupRepository, TaskRepository, StepRepository, UserRepository, NoteRepository, PrintHistoryRepository, ImageRepository } from '../../db/repositories';
import type { Printer } from '@shared/index'; import type { Printer, Group, Task, Step, Note, PrintHistory, User, Image } from '@shared/index';
describe('GraphQL Resolvers', () => { describe('GraphQL Resolvers', () => {
const context = { const context = {
@ -37,18 +37,18 @@ describe('GraphQL Resolvers', () => {
await groupRepo.create({ name: 'Group 1' }); await groupRepo.create({ name: 'Group 1' });
await groupRepo.create({ name: 'Group 2' }); await groupRepo.create({ name: 'Group 2' });
const result = await resolvers.Query.groups(null, null, context); const result = await resolvers.Query.groups(null, {}, context) as Group[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0]?.name).toBe('Group 1'); expect(result[0].name).toBe('Group 1');
expect(result[1]?.name).toBe('Group 2'); expect(result[1].name).toBe('Group 2');
}); });
}); });
describe('group', () => { describe('group', () => {
it('should return a group by id', async () => { it('should return a group by id', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const result = await resolvers.Query.group(null, { id: group.id.toString() }, context); const result = await resolvers.Query.group(null, { id: group.id.toString() }, context) as Group;
expect(result?.name).toBe('Test Group'); expect(result.name).toBe('Test Group');
}); });
it('should return null for non-existent group', async () => { it('should return null for non-existent group', async () => {
@ -63,10 +63,10 @@ describe('GraphQL Resolvers', () => {
await taskRepo.create({ name: 'Task 1', group_id: group.id, print_count: 0 }); await taskRepo.create({ name: 'Task 1', group_id: group.id, print_count: 0 });
await taskRepo.create({ name: 'Task 2', group_id: group.id, print_count: 0 }); await taskRepo.create({ name: 'Task 2', group_id: group.id, print_count: 0 });
const result = await resolvers.Query.tasks(null, { groupId: group.id.toString() }, context); const result = await resolvers.Query.tasks(null, { groupId: group.id.toString() }, context) as Task[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0]?.name).toBe('Task 1'); expect(result[0].name).toBe('Task 1');
expect(result[1]?.name).toBe('Task 2'); expect(result[1].name).toBe('Task 2');
}); });
}); });
@ -74,8 +74,8 @@ describe('GraphQL Resolvers', () => {
it('should return a task by id', async () => { it('should return a task by id', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const result = await resolvers.Query.task(null, { id: task.id.toString() }, context); const result = await resolvers.Query.task(null, { id: task.id.toString() }, context) as Task;
expect(result?.name).toBe('Test Task'); expect(result.name).toBe('Test Task');
}); });
it('should return null for non-existent task', async () => { it('should return null for non-existent task', async () => {
@ -91,10 +91,10 @@ describe('GraphQL Resolvers', () => {
await stepRepo.create({ name: 'Step 1', instructions: 'Instructions 1', task_id: task.id, order: 1, print_count: 0 }); await stepRepo.create({ name: 'Step 1', instructions: 'Instructions 1', task_id: task.id, order: 1, print_count: 0 });
await stepRepo.create({ name: 'Step 2', instructions: 'Instructions 2', task_id: task.id, order: 2, print_count: 0 }); await stepRepo.create({ name: 'Step 2', instructions: 'Instructions 2', task_id: task.id, order: 2, print_count: 0 });
const result = await resolvers.Query.steps(null, { taskId: task.id.toString() }, context); const result = await resolvers.Query.steps(null, { taskId: task.id.toString() }, context) as Step[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0]?.name).toBe('Step 1'); expect(result[0].name).toBe('Step 1');
expect(result[1]?.name).toBe('Step 2'); expect(result[1].name).toBe('Step 2');
}); });
}); });
@ -110,8 +110,8 @@ describe('GraphQL Resolvers', () => {
print_count: 0 print_count: 0
}); });
const result = await resolvers.Query.step(null, { id: step.id.toString() }, context); const result = await resolvers.Query.step(null, { id: step.id.toString() }, context) as Step;
expect(result?.name).toBe('Test Step'); expect(result.name).toBe('Test Step');
}); });
it('should return null for non-existent step', async () => { it('should return null for non-existent step', async () => {
@ -128,10 +128,10 @@ describe('GraphQL Resolvers', () => {
await noteRepo.create({ content: 'Note 1', task_id: task.id, created_by: user.id }); await noteRepo.create({ content: 'Note 1', task_id: task.id, created_by: user.id });
await noteRepo.create({ content: 'Note 2', task_id: task.id, created_by: user.id }); await noteRepo.create({ content: 'Note 2', task_id: task.id, created_by: user.id });
const result = await resolvers.Query.notes(null, { taskId: task.id.toString() }, context); const result = await resolvers.Query.notes(null, { taskId: task.id.toString() }, context) as Note[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0]?.content).toBe('Note 1'); expect(result[0].content).toBe('Note 1');
expect(result[1]?.content).toBe('Note 2'); expect(result[1].content).toBe('Note 2');
}); });
it('should return notes for a step', async () => { it('should return notes for a step', async () => {
@ -148,19 +148,19 @@ describe('GraphQL Resolvers', () => {
await noteRepo.create({ content: 'Note 1', step_id: step.id, created_by: user.id }); await noteRepo.create({ content: 'Note 1', step_id: step.id, created_by: user.id });
await noteRepo.create({ content: 'Note 2', step_id: step.id, created_by: user.id }); await noteRepo.create({ content: 'Note 2', step_id: step.id, created_by: user.id });
const result = await resolvers.Query.notes(null, { stepId: step.id.toString() }, context); const result = await resolvers.Query.notes(null, { stepId: step.id.toString() }, context) as Note[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0]?.content).toBe('Note 1'); expect(result[0].content).toBe('Note 1');
expect(result[1]?.content).toBe('Note 2'); expect(result[1].content).toBe('Note 2');
}); });
it('should return empty array for non-existent task', async () => { it('should return empty array for non-existent task', async () => {
const result = await resolvers.Query.notes(null, { taskId: '999' }, context); const result = await resolvers.Query.notes(null, { taskId: '999' }, context) as Note[];
expect(result).toHaveLength(0); expect(result).toHaveLength(0);
}); });
it('should return empty array for non-existent step', async () => { it('should return empty array for non-existent step', async () => {
const result = await resolvers.Query.notes(null, { stepId: '999' }, context); const result = await resolvers.Query.notes(null, { stepId: '999' }, context) as Note[];
expect(result).toHaveLength(0); expect(result).toHaveLength(0);
}); });
}); });
@ -181,7 +181,7 @@ describe('GraphQL Resolvers', () => {
printed_at: new Date(), printed_at: new Date(),
}); });
const result = await resolvers.Query.printHistory(null, { taskId: task.id.toString() }, context); const result = await resolvers.Query.printHistory(null, { taskId: task.id.toString() }, context) as PrintHistory[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
}); });
@ -207,17 +207,17 @@ describe('GraphQL Resolvers', () => {
printed_at: new Date(), printed_at: new Date(),
}); });
const result = await resolvers.Query.printHistory(null, { stepId: step.id.toString() }, context); const result = await resolvers.Query.printHistory(null, { stepId: step.id.toString() }, context) as PrintHistory[];
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
}); });
it('should return empty array for non-existent task', async () => { it('should return empty array for non-existent task', async () => {
const result = await resolvers.Query.printHistory(null, { taskId: '999' }, context); const result = await resolvers.Query.printHistory(null, { taskId: '999' }, context) as PrintHistory[];
expect(result).toHaveLength(0); expect(result).toHaveLength(0);
}); });
it('should return empty array for non-existent step', async () => { it('should return empty array for non-existent step', async () => {
const result = await resolvers.Query.printHistory(null, { stepId: '999' }, context); const result = await resolvers.Query.printHistory(null, { stepId: '999' }, context) as PrintHistory[];
expect(result).toHaveLength(0); expect(result).toHaveLength(0);
}); });
}); });
@ -227,11 +227,9 @@ describe('GraphQL Resolvers', () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const oldTimestamp = new Date('2020-01-01T00:00:00.000Z'); const oldTimestamp = new Date('2020-01-01T00:00:00.000Z');
const newTimestamp = new Date('2021-01-01T00:00:00.000Z'); const newTimestamp = new Date('2021-01-01T00:00:00.000Z');
console.log('timestamps', oldTimestamp, newTimestamp);
await taskRepo.create({ name: 'Old Task', group_id: group.id, print_count: 0, created_at: oldTimestamp, updated_at: oldTimestamp }); await taskRepo.create({ name: 'Old Task', group_id: group.id, print_count: 0, created_at: oldTimestamp, updated_at: oldTimestamp });
await taskRepo.create({ name: 'New Task', group_id: group.id, print_count: 0, created_at: newTimestamp, updated_at: newTimestamp }); await taskRepo.create({ name: 'New Task', group_id: group.id, print_count: 0, created_at: newTimestamp, updated_at: newTimestamp });
const result = await resolvers.Query.recentTasks(null, null, context); const result = await resolvers.Query.recentTasks(null, {}, context) as Task[];
console.log('result', JSON.stringify(result, null, 2));
expect(result.length).toBeGreaterThanOrEqual(2); expect(result.length).toBeGreaterThanOrEqual(2);
expect(result[0].name).toBe('New Task'); expect(result[0].name).toBe('New Task');
expect(result[1].name).toBe('Old Task'); expect(result[1].name).toBe('Old Task');
@ -243,7 +241,7 @@ describe('GraphQL Resolvers', () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
await taskRepo.create({ name: 'Less Frequent', group_id: group.id, print_count: 1 }); await taskRepo.create({ name: 'Less Frequent', group_id: group.id, print_count: 1 });
await taskRepo.create({ name: 'More Frequent', group_id: group.id, print_count: 10 }); await taskRepo.create({ name: 'More Frequent', group_id: group.id, print_count: 10 });
const result = await resolvers.Query.frequentTasks(null, null, context); const result = await resolvers.Query.frequentTasks(null, {}, context) as Task[];
expect(result.length).toBeGreaterThanOrEqual(2); expect(result.length).toBeGreaterThanOrEqual(2);
expect(result[0].name).toBe('More Frequent'); expect(result[0].name).toBe('More Frequent');
}); });
@ -253,45 +251,25 @@ describe('GraphQL Resolvers', () => {
describe('Mutations', () => { describe('Mutations', () => {
describe('createGroup', () => { describe('createGroup', () => {
it('should create a group', async () => { it('should create a group', async () => {
const result = await resolvers.Mutation.createGroup(null, { name: 'Test Group' }, context); const result = await resolvers.Mutation.createGroup(null, { name: 'Test Group' }, context) as Group;
expect(result?.name).toBe('Test Group'); expect(result.name).toBe('Test Group');
}); });
it('should create a group with parent', async () => { it('should create a child group', async () => {
const parent = await groupRepo.create({ name: 'Parent Group' }); const parent = await groupRepo.create({ name: 'Parent Group' });
const result = await resolvers.Mutation.createGroup( const result = await resolvers.Mutation.createGroup(null, { name: 'Child Group', parentId: parent.id.toString() }, context) as Group;
null,
{ name: 'Child Group', parentId: parent.id.toString() },
context
);
expect(result.name).toBe('Child Group'); expect(result.name).toBe('Child Group');
expect(result.parent_id).toBe(parent.id); expect(result.parent_id).toBe(parent.id);
}); });
it('should throw error for non-existent parent', async () => {
await expect(
resolvers.Mutation.createGroup(null, { name: 'Child Group', parentId: '999' }, context)
).rejects.toThrow('Parent group not found');
});
}); });
describe('createTask', () => { describe('createTask', () => {
it('should create a task', async () => { it('should create a task', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const result = await resolvers.Mutation.createTask( const result = await resolvers.Mutation.createTask(null, { name: 'Test Task', groupId: group.id.toString() }, context) as Task;
null,
{ name: 'Test Task', groupId: group.id.toString() },
context
);
expect(result.name).toBe('Test Task'); expect(result.name).toBe('Test Task');
expect(result.group_id).toBe(group.id); expect(result.group_id).toBe(group.id);
}); });
it('should throw error for non-existent group', async () => {
await expect(
resolvers.Mutation.createTask(null, { name: 'Test Task', groupId: '999' }, context)
).rejects.toThrow('Group not found');
});
}); });
describe('createStep', () => { describe('createStep', () => {
@ -300,32 +278,12 @@ describe('GraphQL Resolvers', () => {
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const result = await resolvers.Mutation.createStep( const result = await resolvers.Mutation.createStep(
null, null,
{ { name: 'Test Step', instructions: 'Test Instructions', taskId: task.id.toString(), order: 1 },
name: 'Test Step',
instructions: 'Test Instructions',
taskId: task.id.toString(),
order: 1,
},
context context
); ) as Step;
expect(result.name).toBe('Test Step'); expect(result.name).toBe('Test Step');
expect(result.task_id).toBe(task.id); expect(result.task_id).toBe(task.id);
}); });
it('should throw error for non-existent task', async () => {
await expect(
resolvers.Mutation.createStep(
null,
{
name: 'Test Step',
instructions: 'Test Instructions',
taskId: '999',
order: 1,
},
context
)
).rejects.toThrow('Task not found');
});
}); });
describe('createImage', () => { describe('createImage', () => {
@ -341,32 +299,19 @@ describe('GraphQL Resolvers', () => {
}); });
const result = await resolvers.Mutation.createImage( const result = await resolvers.Mutation.createImage(
null, null,
{ { stepId: step.id.toString(), originalPath: '/path/to/image.jpg', bwPath: '/path/to/image.bw.jpg', order: 1 },
stepId: step.id.toString(),
originalPath: '/path/to/image.jpg',
bwPath: '/path/to/image.bw.jpg',
order: 1,
},
context context
); ) as Image;
expect(result.original_path).toBe('/path/to/image.jpg'); expect(result.original_path).toBe('/path/to/image.jpg');
expect(result.bw_path).toBe('/path/to/image.bw.jpg'); expect(result.bw_path).toBe('/path/to/image.bw.jpg');
expect(result.step_id).toBe(step.id); expect(result.step_id).toBe(step.id);
}); });
});
it('should throw error for non-existent step', async () => { describe('createUser', () => {
await expect( it('should create a user', async () => {
resolvers.Mutation.createImage( const result = await resolvers.Mutation.createUser(null, { name: 'Test User' }, context) as User;
null, expect(result.name).toBe('Test User');
{
stepId: '999',
originalPath: '/path/to/image.jpg',
bwPath: '/path/to/image.bw.jpg',
order: 1,
},
context
)
).rejects.toThrow('Step not found');
}); });
}); });
@ -377,13 +322,9 @@ describe('GraphQL Resolvers', () => {
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
const result = await resolvers.Mutation.createNote( const result = await resolvers.Mutation.createNote(
null, null,
{ { content: 'Test Note', taskId: task.id.toString(), userId: user.id.toString() },
content: 'Test Note',
taskId: task.id.toString(),
userId: user.id.toString(),
},
context context
); ) as Note;
expect(result.content).toBe('Test Note'); expect(result.content).toBe('Test Note');
expect(result.task_id).toBe(task.id); expect(result.task_id).toBe(task.id);
expect(result.created_by).toBe(user.id); expect(result.created_by).toBe(user.id);
@ -402,70 +343,13 @@ describe('GraphQL Resolvers', () => {
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
const result = await resolvers.Mutation.createNote( const result = await resolvers.Mutation.createNote(
null, null,
{ { content: 'Test Note', stepId: step.id.toString(), userId: user.id.toString() },
content: 'Test Note',
stepId: step.id.toString(),
userId: user.id.toString(),
},
context context
); ) as Note;
expect(result.content).toBe('Test Note'); expect(result.content).toBe('Test Note');
expect(result.step_id).toBe(step.id); expect(result.step_id).toBe(step.id);
expect(result.created_by).toBe(user.id); expect(result.created_by).toBe(user.id);
}); });
it('should throw error for non-existent task', async () => {
const user = await userRepo.create({ name: 'Test User' });
await expect(
resolvers.Mutation.createNote(
null,
{
content: 'Test Note',
taskId: '999',
userId: user.id.toString(),
},
context
)
).rejects.toThrow('Task not found');
});
it('should throw error for non-existent step', async () => {
const user = await userRepo.create({ name: 'Test User' });
await expect(
resolvers.Mutation.createNote(
null,
{
content: 'Test Note',
stepId: '999',
userId: user.id.toString(),
},
context
)
).rejects.toThrow('Step not found');
});
it('should throw error for non-existent user', async () => {
const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({
name: 'Test Step',
instructions: 'Test Instructions',
task_id: task.id,
order: 1,
print_count: 0
});
await expect(
resolvers.Mutation.createNote(
null,
{
content: 'Test Note',
stepId: step.id.toString(),
userId: '999',
},
context
)
).rejects.toThrow('User not found');
});
}); });
describe('printTask', () => { describe('printTask', () => {
@ -473,31 +357,9 @@ describe('GraphQL Resolvers', () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
const result = await resolvers.Mutation.printTask( const result = await resolvers.Mutation.printTask(null, { id: task.id.toString(), userId: user.id.toString() }, context) as Task;
null,
{ id: task.id.toString(), userId: user.id.toString() },
context
);
if (!result) throw new Error('printTask mutation did not return a task');
expect(result).not.toBeNull();
expect(result && result.print_count).toBe(1); expect(result && result.print_count).toBe(1);
expect(result && result.last_printed_at).toBeDefined(); expect(result && result.last_printed_at).toBeDefined();
expect(context.printer.printTask).toHaveBeenCalled();
});
it('should throw error for non-existent task', async () => {
const user = await userRepo.create({ name: 'Test User' });
await expect(
resolvers.Mutation.printTask(null, { id: '999', userId: user.id.toString() }, context)
).rejects.toThrow('Task not found');
});
it('should throw error for non-existent user', async () => {
const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
await expect(
resolvers.Mutation.printTask(null, { id: task.id.toString(), userId: '999' }, context)
).rejects.toThrow('User not found');
}); });
}); });
@ -513,51 +375,21 @@ describe('GraphQL Resolvers', () => {
print_count: 0 print_count: 0
}); });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
const result = await resolvers.Mutation.printStep( const result = await resolvers.Mutation.printStep(null, { id: step.id.toString(), userId: user.id.toString() }, context) as Step;
null,
{ id: step.id.toString(), userId: user.id.toString() },
context
);
if (!result) throw new Error('printStep mutation did not return a step');
expect(result).not.toBeNull();
expect(result && result.print_count).toBe(1); expect(result && result.print_count).toBe(1);
expect(result && result.last_printed_at).toBeDefined(); expect(result && result.last_printed_at).toBeDefined();
expect(context.printer.printStep).toHaveBeenCalled();
});
it('should throw error for non-existent step', async () => {
const user = await userRepo.create({ name: 'Test User' });
await expect(
resolvers.Mutation.printStep(null, { id: '999', userId: user.id.toString() }, context)
).rejects.toThrow('Step not found');
});
it('should throw error for non-existent user', async () => {
const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({
name: 'Test Step',
instructions: 'Test Instructions',
task_id: task.id,
order: 1,
print_count: 0
});
await expect(
resolvers.Mutation.printStep(null, { id: step.id.toString(), userId: '999' }, context)
).rejects.toThrow('User not found');
}); });
}); });
}); });
describe('Field Resolvers', () => { describe('Field Resolvers', () => {
describe('Group', () => { describe('Group', () => {
it('should resolve tasks field', async () => { it('should resolve tasks', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
await taskRepo.create({ name: 'Task 1', group_id: group.id, print_count: 0 }); await taskRepo.create({ name: 'Task 1', group_id: group.id, print_count: 0 });
await taskRepo.create({ name: 'Task 2', group_id: group.id, print_count: 0 }); await taskRepo.create({ name: 'Task 2', group_id: group.id, print_count: 0 });
if (!group) throw new Error('Group not found'); const result = await resolvers.Group.tasks(group, {}, context) as Task[];
const result = await resolvers.Group.tasks(group, null, context);
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0].name).toBe('Task 1'); expect(result[0].name).toBe('Task 1');
expect(result[1].name).toBe('Task 2'); expect(result[1].name).toBe('Task 2');
@ -565,66 +397,42 @@ describe('GraphQL Resolvers', () => {
}); });
describe('Task', () => { describe('Task', () => {
it('should resolve group field', async () => { it('should resolve group', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
expect(task).not.toBeNull(); const result = await resolvers.Task.group(task, {}, context) as Group;
const result = await resolvers.Task.group(task!, null, context); expect(result.name).toBe('Test Group');
expect(result).not.toBeNull();
expect(result!.name).toBe('Test Group');
}); });
it('should resolve steps field', async () => { it('should resolve steps', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
await stepRepo.create({ name: 'Step 1', instructions: 'Instructions 1', task_id: task.id, order: 1, print_count: 0 }); await stepRepo.create({ name: 'Step 1', instructions: 'Test', task_id: task.id, order: 1, print_count: 0 });
await stepRepo.create({ name: 'Step 2', instructions: 'Instructions 2', task_id: task.id, order: 2, print_count: 0 }); await stepRepo.create({ name: 'Step 2', instructions: 'Test', task_id: task.id, order: 2, print_count: 0 });
expect(task).not.toBeNull(); const result = await resolvers.Task.steps(task, {}, context) as Step[];
const result = await resolvers.Task.steps(task!, null, context);
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0].name).toBe('Step 1'); expect(result[0].name).toBe('Step 1');
expect(result[1].name).toBe('Step 2'); expect(result[1].name).toBe('Step 2');
}); });
it('should resolve notes field', async () => { it('should resolve notes', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
await noteRepo.create({ content: 'Note 1', task_id: task.id, created_by: user.id }); await noteRepo.create({ content: 'Note 1', task_id: task.id, created_by: user.id });
await noteRepo.create({ content: 'Note 2', task_id: task.id, created_by: user.id }); await noteRepo.create({ content: 'Note 2', task_id: task.id, created_by: user.id });
expect(task).not.toBeNull(); const result = await resolvers.Task.notes(task, {}, context) as Note[];
const result = await resolvers.Task.notes(task!, null, context);
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0].content).toBe('Note 1'); expect(result[0].content).toBe('Note 1');
expect(result[1].content).toBe('Note 2'); expect(result[1].content).toBe('Note 2');
}); });
it('should resolve printHistory field', async () => {
const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const user = await userRepo.create({ name: 'Test User' });
await printHistoryRepo.create({
user_id: user.id,
task_id: task.id,
printed_at: new Date(),
});
await printHistoryRepo.create({
user_id: user.id,
task_id: task.id,
printed_at: new Date(),
});
expect(task).not.toBeNull();
const result = await resolvers.Task.printHistory(task!, null, context);
expect(result).toHaveLength(2);
});
}); });
describe('Step', () => { describe('Step', () => {
it('should resolve task field', async () => { it('should resolve task', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({ const step = await stepRepo.create({
@ -635,13 +443,11 @@ describe('GraphQL Resolvers', () => {
print_count: 0 print_count: 0
}); });
expect(step).not.toBeNull(); const result = await resolvers.Step.task(step, {}, context) as Task;
const result = await resolvers.Step.task(step!, null, context); expect(result.name).toBe('Test Task');
expect(result).not.toBeNull();
expect(result!.name).toBe('Test Task');
}); });
it('should resolve images field', async () => { it('should resolve images', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({ const step = await stepRepo.create({
@ -651,27 +457,16 @@ describe('GraphQL Resolvers', () => {
order: 1, order: 1,
print_count: 0 print_count: 0
}); });
await imageRepo.create({ await imageRepo.create({ step_id: step.id, original_path: '/path/to/original1.jpg', bw_path: '/path/to/bw1.jpg', order: 1 });
step_id: step.id, await imageRepo.create({ step_id: step.id, original_path: '/path/to/original2.jpg', bw_path: '/path/to/bw2.jpg', order: 2 });
original_path: '/path/to/original1.jpg',
bw_path: '/path/to/bw1.jpg',
order: 1,
});
await imageRepo.create({
step_id: step.id,
original_path: '/path/to/original2.jpg',
bw_path: '/path/to/bw2.jpg',
order: 2,
});
expect(step).not.toBeNull(); const result = await resolvers.Step.images(step, {}, context) as Image[];
const result = await resolvers.Step.images(step!, null, context);
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0].original_path).toBe('/path/to/original1.jpg'); expect(result[0].original_path).toBe('/path/to/original1.jpg');
expect(result[1].original_path).toBe('/path/to/original2.jpg'); expect(result[1].original_path).toBe('/path/to/original2.jpg');
}); });
it('should resolve notes field', async () => { it('should resolve notes', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({ const step = await stepRepo.create({
@ -685,96 +480,55 @@ describe('GraphQL Resolvers', () => {
await noteRepo.create({ content: 'Note 1', step_id: step.id, created_by: user.id }); await noteRepo.create({ content: 'Note 1', step_id: step.id, created_by: user.id });
await noteRepo.create({ content: 'Note 2', step_id: step.id, created_by: user.id }); await noteRepo.create({ content: 'Note 2', step_id: step.id, created_by: user.id });
expect(step).not.toBeNull(); const result = await resolvers.Step.notes(step, {}, context) as Note[];
const result = await resolvers.Step.notes(step!, null, context);
expect(result).toHaveLength(2); expect(result).toHaveLength(2);
expect(result[0].content).toBe('Note 1'); expect(result[0].content).toBe('Note 1');
expect(result[1].content).toBe('Note 2'); expect(result[1].content).toBe('Note 2');
}); });
it('should resolve printHistory field', async () => {
const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({
name: 'Test Step',
instructions: 'Test Instructions',
task_id: task.id,
order: 1,
print_count: 0
});
const user = await userRepo.create({ name: 'Test User' });
await printHistoryRepo.create({
user_id: user.id,
step_id: step.id,
printed_at: new Date(),
});
await printHistoryRepo.create({
user_id: user.id,
step_id: step.id,
printed_at: new Date(),
});
expect(step).not.toBeNull();
const result = await resolvers.Step.printHistory(step!, null, context);
expect(result).toHaveLength(2);
});
}); });
describe('Note', () => { describe('Note', () => {
it('should resolve createdBy field', async () => { it('should resolve createdBy', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
await noteRepo.create({ const note = await noteRepo.create({ content: 'Test Note', task_id: task.id, created_by: user.id });
content: 'Test Note',
task_id: task.id,
created_by: user.id,
});
const notes = await noteRepo.findByTaskId(task.id); const result = await resolvers.Note.createdBy(note, {}, context) as User;
expect(notes).toHaveLength(1); expect(result.name).toBe('Test User');
const result = await resolvers.Note.createdBy(notes[0], null, context);
expect(result).not.toBeNull();
expect(result!.name).toBe('Test User');
}); });
}); });
describe('PrintHistory', () => { describe('PrintHistory', () => {
it('should resolve user field', async () => { it('should resolve user', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
await printHistoryRepo.create({ const history = await printHistoryRepo.create({
user_id: user.id, user_id: user.id,
task_id: task.id, task_id: task.id,
printed_at: new Date(), printed_at: new Date(),
}); });
const history = await printHistoryRepo.findByTaskId(task.id); const result = await resolvers.PrintHistory.user(history, {}, context) as User;
expect(history).toHaveLength(1); expect(result.name).toBe('Test User');
const result = await resolvers.PrintHistory.user(history[0], null, context);
expect(result).not.toBeNull();
expect(result!.name).toBe('Test User');
}); });
it('should resolve task field', async () => { it('should resolve task', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
await printHistoryRepo.create({ const history = await printHistoryRepo.create({
user_id: user.id, user_id: user.id,
task_id: task.id, task_id: task.id,
printed_at: new Date(), printed_at: new Date(),
}); });
const history = await printHistoryRepo.findByTaskId(task.id); const result = await resolvers.PrintHistory.task(history, {}, context) as Task;
expect(history).toHaveLength(1); expect(result.name).toBe('Test Task');
const result = await resolvers.PrintHistory.task(history[0], null, context);
expect(result).not.toBeNull();
expect(result!.name).toBe('Test Task');
}); });
it('should resolve step field', async () => { it('should resolve step', async () => {
const group = await groupRepo.create({ name: 'Test Group' }); const group = await groupRepo.create({ name: 'Test Group' });
const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 }); const task = await taskRepo.create({ name: 'Test Task', group_id: group.id, print_count: 0 });
const step = await stepRepo.create({ const step = await stepRepo.create({
@ -785,17 +539,14 @@ describe('GraphQL Resolvers', () => {
print_count: 0 print_count: 0
}); });
const user = await userRepo.create({ name: 'Test User' }); const user = await userRepo.create({ name: 'Test User' });
await printHistoryRepo.create({ const history = await printHistoryRepo.create({
user_id: user.id, user_id: user.id,
step_id: step.id, step_id: step.id,
printed_at: new Date(), printed_at: new Date(),
}); });
const history = await printHistoryRepo.findByStepId(step.id); const result = await resolvers.PrintHistory.step(history, {}, context) as Step;
expect(history).toHaveLength(1); expect(result.name).toBe('Test Step');
const result = await resolvers.PrintHistory.step(history[0], null, context);
expect(result).not.toBeNull();
expect(result!.name).toBe('Test Step');
}); });
}); });
}); });

View File

@ -1,56 +1,61 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { GroupRepository, TaskRepository, StepRepository, UserRepository, NoteRepository, PrintHistoryRepository, ImageRepository } from '../db/repositories'; import { GroupRepository, TaskRepository, StepRepository, UserRepository, NoteRepository, PrintHistoryRepository, ImageRepository } from '../db/repositories';
import { printTask, printStep } from '../printer/helpers'; import { printTask, printStep } from '../printer/helpers';
import type { Printer } from '@shared/index'; import type { Printer, Group, Task, Step, User, Note, PrintHistory, Image } from '@shared/index';
interface Context { interface Context {
db: Knex; db: Knex;
printer: Printer; printer: Printer;
} }
// GraphQL resolver types
type ResolverParent = unknown;
type ResolverArgs = Record<string, unknown>;
type ResolverResult<T> = Promise<T | null>;
export const resolvers = { export const resolvers = {
Query: { Query: {
groups: async (_: any, __: any, { db }: Context) => { groups: async (_parent: ResolverParent, _args: ResolverArgs, { db }: Context): ResolverResult<Group[]> => {
const groupRepo = new GroupRepository(db); const groupRepo = new GroupRepository(db);
return await groupRepo.findAll(); return await groupRepo.findAll();
}, },
group: async (_: any, { id }: { id: string }, { db }: Context) => { group: async (_parent: ResolverParent, { id }: { id: string }, { db }: Context): ResolverResult<Group> => {
const groupRepo = new GroupRepository(db); const groupRepo = new GroupRepository(db);
return await groupRepo.findById(parseInt(id)); return await groupRepo.findById(parseInt(id));
}, },
tasks: async (_: any, { groupId }: { groupId: string }, { db }: Context) => { tasks: async (_parent: ResolverParent, { groupId }: { groupId: string }, { db }: Context): ResolverResult<Task[]> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findByGroupId(parseInt(groupId)); return await taskRepo.findByGroupId(parseInt(groupId));
}, },
task: async (_: any, { id }: { id: string }, { db }: Context) => { task: async (_parent: ResolverParent, { id }: { id: string }, { db }: Context): ResolverResult<Task> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findById(parseInt(id)); return await taskRepo.findById(parseInt(id));
}, },
steps: async (_: any, { taskId }: { taskId: string }, { db }: Context) => { steps: async (_parent: ResolverParent, { taskId }: { taskId: string }, { db }: Context): ResolverResult<Step[]> => {
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
return await stepRepo.findByTaskId(parseInt(taskId)); return await stepRepo.findByTaskId(parseInt(taskId));
}, },
step: async (_: any, { id }: { id: string }, { db }: Context) => { step: async (_parent: ResolverParent, { id }: { id: string }, { db }: Context): ResolverResult<Step> => {
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
return await stepRepo.findById(parseInt(id)); return await stepRepo.findById(parseInt(id));
}, },
recentTasks: async (_: any, __: any, { db }: Context) => { recentTasks: async (_parent: ResolverParent, _args: ResolverArgs, { db }: Context): ResolverResult<Task[]> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findRecent(); return await taskRepo.findRecent();
}, },
frequentTasks: async (_: any, __: any, { db }: Context) => { frequentTasks: async (_parent: ResolverParent, _args: ResolverArgs, { db }: Context): ResolverResult<Task[]> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findFrequent(); return await taskRepo.findFrequent();
}, },
users: async (_: any, __: any, { db }: Context) => { users: async (_parent: ResolverParent, _args: ResolverArgs, { db }: Context): ResolverResult<User[]> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
return await userRepo.findAll(); return await userRepo.findAll();
}, },
user: async (_: any, { id }: { id: string }, { db }: Context) => { user: async (_parent: ResolverParent, { id }: { id: string }, { db }: Context): ResolverResult<User> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
return await userRepo.findById(parseInt(id)); return await userRepo.findById(parseInt(id));
}, },
notes: async (_: any, { stepId, taskId }: { stepId?: string; taskId?: string }, { db }: Context) => { notes: async (_parent: ResolverParent, { stepId, taskId }: { stepId?: string; taskId?: string }, { db }: Context): ResolverResult<Note[]> => {
const noteRepo = new NoteRepository(db); const noteRepo = new NoteRepository(db);
if (stepId) { if (stepId) {
return await noteRepo.findByStepId(parseInt(stepId)); return await noteRepo.findByStepId(parseInt(stepId));
@ -60,7 +65,7 @@ export const resolvers = {
} }
return await noteRepo.findAll(); return await noteRepo.findAll();
}, },
printHistory: async (_: any, { taskId, stepId }: { taskId?: string; stepId?: string }, { db }: Context) => { printHistory: async (_parent: ResolverParent, { taskId, stepId }: { taskId?: string; stepId?: string }, { db }: Context): ResolverResult<PrintHistory[]> => {
const printHistoryRepo = new PrintHistoryRepository(db); const printHistoryRepo = new PrintHistoryRepository(db);
if (taskId) { if (taskId) {
return await printHistoryRepo.findByTaskId(parseInt(taskId)); return await printHistoryRepo.findByTaskId(parseInt(taskId));
@ -73,7 +78,7 @@ export const resolvers = {
}, },
Mutation: { Mutation: {
createGroup: async (_: any, { name, parentId }: { name: string; parentId?: string }, { db }: Context) => { createGroup: async (_parent: ResolverParent, { name, parentId }: { name: string; parentId?: string }, { db }: Context): ResolverResult<Group> => {
const groupRepo = new GroupRepository(db); const groupRepo = new GroupRepository(db);
let parent_id: number | undefined = undefined; let parent_id: number | undefined = undefined;
if (parentId) { if (parentId) {
@ -83,7 +88,7 @@ export const resolvers = {
} }
return await groupRepo.create({ name, parent_id }); return await groupRepo.create({ name, parent_id });
}, },
createTask: async (_: any, { name, groupId }: { name: string; groupId: string }, { db }: Context) => { createTask: async (_parent: ResolverParent, { name, groupId }: { name: string; groupId: string }, { db }: Context): ResolverResult<Task> => {
const groupRepo = new GroupRepository(db); const groupRepo = new GroupRepository(db);
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
const group = await groupRepo.findById(parseInt(groupId)); const group = await groupRepo.findById(parseInt(groupId));
@ -94,7 +99,7 @@ export const resolvers = {
print_count: 0, print_count: 0,
}); });
}, },
createStep: async (_: any, { name, instructions, taskId, order }: { name: string; instructions: string; taskId: string; order: number }, { db }: Context) => { createStep: async (_parent: ResolverParent, { name, instructions, taskId, order }: { name: string; instructions: string; taskId: string; order: number }, { db }: Context): ResolverResult<Step> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
const task = await taskRepo.findById(parseInt(taskId)); const task = await taskRepo.findById(parseInt(taskId));
@ -107,7 +112,7 @@ export const resolvers = {
print_count: 0, print_count: 0,
}); });
}, },
createImage: async (_: any, { stepId, originalPath, bwPath, order }: { stepId: string; originalPath: string; bwPath: string; order: number }, { db }: Context) => { createImage: async (_parent: ResolverParent, { stepId, originalPath, bwPath, order }: { stepId: string; originalPath: string; bwPath: string; order: number }, { db }: Context): ResolverResult<Image> => {
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
const imageRepo = new ImageRepository(db); const imageRepo = new ImageRepository(db);
const step = await stepRepo.findById(parseInt(stepId)); const step = await stepRepo.findById(parseInt(stepId));
@ -119,15 +124,15 @@ export const resolvers = {
order, order,
}); });
}, },
createUser: async (_: any, { name }: { name: string }, { db }: Context) => { createUser: async (_parent: ResolverParent, { name }: { name: string }, { db }: Context): ResolverResult<User> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
return await userRepo.create({ name }); return await userRepo.create({ name });
}, },
createNote: async ( createNote: async (
_: any, _parent: ResolverParent,
{ content, stepId, taskId, userId }: { content: string; stepId?: string; taskId?: string; userId: string }, { content, stepId, taskId, userId }: { content: string; stepId?: string; taskId?: string; userId: string },
{ db }: Context { db }: Context
) => { ): ResolverResult<Note> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
@ -148,13 +153,13 @@ export const resolvers = {
created_by: parseInt(userId), created_by: parseInt(userId),
}); });
}, },
printTask: async (_: any, { id, userId }: { id: string; userId: string }, { db, printer }: Context) => { printTask: async (_parent: ResolverParent, { id, userId }: { id: string; userId: string }, { db, printer }: Context): ResolverResult<Task> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
const user = await userRepo.findById(parseInt(userId)); const user = await userRepo.findById(parseInt(userId));
if (!user) throw new Error('User not found'); if (!user) throw new Error('User not found');
return await printTask(parseInt(id), parseInt(userId), db, printer); return await printTask(parseInt(id), parseInt(userId), db, printer);
}, },
printStep: async (_: any, { id, userId }: { id: string; userId: string }, { db, printer }: Context) => { printStep: async (_parent: ResolverParent, { id, userId }: { id: string; userId: string }, { db, printer }: Context): ResolverResult<Step> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
const user = await userRepo.findById(parseInt(userId)); const user = await userRepo.findById(parseInt(userId));
if (!user) throw new Error('User not found'); if (!user) throw new Error('User not found');
@ -163,68 +168,68 @@ export const resolvers = {
}, },
Group: { Group: {
tasks: async (group: { id: number }, _: any, { db }: Context) => { tasks: async (group: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Task[]> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findByGroupId(group.id); return await taskRepo.findByGroupId(group.id);
}, },
}, },
Task: { Task: {
group: async (task: { group_id: number }, _: any, { db }: Context) => { group: async (task: { group_id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Group> => {
const groupRepo = new GroupRepository(db); const groupRepo = new GroupRepository(db);
return await groupRepo.findById(task.group_id); return await groupRepo.findById(task.group_id);
}, },
steps: async (task: { id: number }, _: any, { db }: Context) => { steps: async (task: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Step[]> => {
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
return await stepRepo.findByTaskId(task.id); return await stepRepo.findByTaskId(task.id);
}, },
notes: async (task: { id: number }, _: any, { db }: Context) => { notes: async (task: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Note[]> => {
const noteRepo = new NoteRepository(db); const noteRepo = new NoteRepository(db);
return await noteRepo.findByTaskId(task.id); return await noteRepo.findByTaskId(task.id);
}, },
printHistory: async (task: { id: number }, _: any, { db }: Context) => { printHistory: async (task: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<PrintHistory[]> => {
const printHistoryRepo = new PrintHistoryRepository(db); const printHistoryRepo = new PrintHistoryRepository(db);
return await printHistoryRepo.findByTaskId(task.id); return await printHistoryRepo.findByTaskId(task.id);
}, },
}, },
Step: { Step: {
task: async (step: { task_id: number }, _: any, { db }: Context) => { task: async (step: { task_id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Task> => {
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findById(step.task_id); return await taskRepo.findById(step.task_id);
}, },
images: async (step: { id: number }, _: any, { db }: Context) => { images: async (step: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Image[]> => {
const imageRepo = new ImageRepository(db); const imageRepo = new ImageRepository(db);
return await imageRepo.findByStepId(step.id); return await imageRepo.findByStepId(step.id);
}, },
notes: async (step: { id: number }, _: any, { db }: Context) => { notes: async (step: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Note[]> => {
const noteRepo = new NoteRepository(db); const noteRepo = new NoteRepository(db);
return await noteRepo.findByStepId(step.id); return await noteRepo.findByStepId(step.id);
}, },
printHistory: async (step: { id: number }, _: any, { db }: Context) => { printHistory: async (step: { id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<PrintHistory[]> => {
const printHistoryRepo = new PrintHistoryRepository(db); const printHistoryRepo = new PrintHistoryRepository(db);
return await printHistoryRepo.findByStepId(step.id); return await printHistoryRepo.findByStepId(step.id);
}, },
}, },
Note: { Note: {
createdBy: async (note: { created_by: number }, _: any, { db }: Context) => { createdBy: async (note: { created_by: number }, _args: ResolverArgs, { db }: Context): ResolverResult<User> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
return await userRepo.findById(note.created_by); return await userRepo.findById(note.created_by);
}, },
}, },
PrintHistory: { PrintHistory: {
user: async (history: { user_id: number }, _: any, { db }: Context) => { user: async (history: { user_id: number }, _args: ResolverArgs, { db }: Context): ResolverResult<User> => {
const userRepo = new UserRepository(db); const userRepo = new UserRepository(db);
return await userRepo.findById(history.user_id); return await userRepo.findById(history.user_id);
}, },
task: async (history: { task_id?: number }, _: any, { db }: Context) => { task: async (history: { task_id?: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Task> => {
if (!history.task_id) return null; if (!history.task_id) return null;
const taskRepo = new TaskRepository(db); const taskRepo = new TaskRepository(db);
return await taskRepo.findById(history.task_id); return await taskRepo.findById(history.task_id);
}, },
step: async (history: { step_id?: number }, _: any, { db }: Context) => { step: async (history: { step_id?: number }, _args: ResolverArgs, { db }: Context): ResolverResult<Step> => {
if (!history.step_id) return null; if (!history.step_id) return null;
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
return await stepRepo.findById(history.step_id); return await stepRepo.findById(history.step_id);

View File

@ -1,23 +1,25 @@
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import { TestPrinter } from '../index'; import { TestPrinter } from '../index';
import { PrintHistoryRepository, StepRepository } from '../../db/repositories';
import { Task, Step } from '@shared/index'; import { Task, Step } from '@shared/index';
import { InMemoryPrintHistoryRepository, InMemoryStepRepository } from '../../db/repositories/in-memory-repository'; import { InMemoryPrintHistoryRepository, InMemoryStepRepository } from '../../db/repositories/in-memory-repository';
import { Knex } from 'knex';
describe('Printer', () => { describe('Printer', () => {
let printHistoryRepo: jest.Mocked<PrintHistoryRepository>; let printHistoryRepo: InMemoryPrintHistoryRepository;
let stepRepo: StepRepository; let stepRepo: InMemoryStepRepository;
let testPrinter: TestPrinter; let testPrinter: TestPrinter;
let mockDb: jest.Mocked<Knex>;
beforeEach(() => { beforeEach(() => {
printHistoryRepo = new InMemoryPrintHistoryRepository() as any; mockDb = {} as jest.Mocked<Knex>;
stepRepo = new InMemoryStepRepository() as any; printHistoryRepo = new InMemoryPrintHistoryRepository(mockDb);
stepRepo = new InMemoryStepRepository(mockDb);
testPrinter = new TestPrinter(printHistoryRepo, stepRepo); testPrinter = new TestPrinter(printHistoryRepo, stepRepo);
}); });
it('should record print history when printing a task', async () => { it('should record print history when printing a task', async () => {
const task: Task = { id: 1, name: 'Test Task', group_id: 1, print_count: 0, created_at: new Date(), updated_at: new Date() }; const task: Task = { id: 1, name: 'Test Task', group_id: 1, print_count: 0, created_at: new Date(), updated_at: new Date() };
await testPrinter.printTask(task, {} as any); await testPrinter.printTask(task, mockDb);
const allHistory = await printHistoryRepo.findAll(); const allHistory = await printHistoryRepo.findAll();
expect(allHistory.length).toBe(1); expect(allHistory.length).toBe(1);
expect(allHistory[0]).toMatchObject({ expect(allHistory[0]).toMatchObject({
@ -29,7 +31,7 @@ describe('Printer', () => {
it('should record print history when printing a step', async () => { it('should record print history when printing a step', async () => {
const step: Step = { id: 1, name: 'Test Step', instructions: 'Test Instructions', task_id: 1, order: 1, print_count: 0, created_at: new Date(), updated_at: new Date() }; const step: Step = { id: 1, name: 'Test Step', instructions: 'Test Instructions', task_id: 1, order: 1, print_count: 0, created_at: new Date(), updated_at: new Date() };
await testPrinter.printStep(step, {} as any); await testPrinter.printStep(step, mockDb);
const allHistory = await printHistoryRepo.findAll(); const allHistory = await printHistoryRepo.findAll();
expect(allHistory.length).toBe(1); expect(allHistory.length).toBe(1);
expect(allHistory[0]).toMatchObject({ expect(allHistory[0]).toMatchObject({

View File

@ -1,5 +1,5 @@
import { Printer } from "@node-escpos/core"; import { Printer } from '@node-escpos/core';
import USB from "@node-escpos/usb-adapter"; import USB from '@node-escpos/usb-adapter';
import { Task, Step, Printer as PrinterInterface } from '@shared/index'; import { Task, Step, Printer as PrinterInterface } from '@shared/index';
import { StepRepository, PrintHistoryRepository } from '../db/repositories'; import { StepRepository, PrintHistoryRepository } from '../db/repositories';
import { Knex } from 'knex'; import { Knex } from 'knex';
@ -31,7 +31,7 @@ export class SerialPrinter implements PrinterInterface {
}); });
}); });
const options = { encoding: "CP437" }; const options = { encoding: 'CP437' };
this.printer = new Printer(this.device, options); this.printer = new Printer(this.device, options);
logger.info('Printer initialized successfully'); logger.info('Printer initialized successfully');
} catch (error) { } catch (error) {
@ -39,7 +39,7 @@ export class SerialPrinter implements PrinterInterface {
} }
} }
async getTaskSteps(db: Knex, task: Task): Promise<Step[]> { async getTaskSteps(_db: Knex, task: Task): Promise<Step[]> {
return await this.stepRepository.findByTaskId(task.id); return await this.stepRepository.findByTaskId(task.id);
} }
@ -104,7 +104,7 @@ export class SerialPrinter implements PrinterInterface {
} }
} }
async printStep(step: Step, db: Knex): Promise<void> { async printStep(step: Step, _db: Knex): Promise<void> {
if (!this.printer || !this.device) { if (!this.printer || !this.device) {
throw new Error('Printer not initialized'); throw new Error('Printer not initialized');
} }