get the server working
This commit is contained in:
parent
3d02f532d9
commit
fd9f9e3d85
@ -19,6 +19,7 @@ module.exports = {
|
||||
'src/**/*.{ts,js}',
|
||||
'!**/__tests__/**',
|
||||
'!**/node_modules/**',
|
||||
'!src/index.ts',
|
||||
'!src/printer/serial-printer.ts',
|
||||
],
|
||||
coverageThreshold: {
|
||||
|
@ -0,0 +1,126 @@
|
||||
import { InMemoryRepository, InMemoryPrintHistoryRepository, InMemoryStepRepository } from '../in-memory-repository';
|
||||
|
||||
describe('InMemoryRepository', () => {
|
||||
let repository: InMemoryRepository<{ id: number; name: string }>;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryRepository();
|
||||
});
|
||||
|
||||
it('should create and find items', async () => {
|
||||
const item = await repository.create({ name: 'test' });
|
||||
expect(item.id).toBe(1);
|
||||
expect(item.name).toBe('test');
|
||||
|
||||
const found = await repository.findById(item.id);
|
||||
expect(found).toEqual(item);
|
||||
});
|
||||
|
||||
it('should return null for non-existent id', async () => {
|
||||
const found = await repository.findById(999);
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should update items', async () => {
|
||||
const item = await repository.create({ name: 'test' });
|
||||
const success = await repository.update(item.id, { name: 'updated' });
|
||||
expect(success).toBe(true);
|
||||
|
||||
const updated = await repository.findById(item.id);
|
||||
expect(updated?.name).toBe('updated');
|
||||
});
|
||||
|
||||
it('should return false when updating non-existent item', async () => {
|
||||
const success = await repository.update(999, { name: 'updated' });
|
||||
expect(success).toBe(false);
|
||||
});
|
||||
|
||||
it('should delete items', async () => {
|
||||
const item = await repository.create({ name: 'test' });
|
||||
const success = await repository.delete(item.id);
|
||||
expect(success).toBe(true);
|
||||
|
||||
const found = await repository.findById(item.id);
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should return false when deleting non-existent item', async () => {
|
||||
const success = await repository.delete(999);
|
||||
expect(success).toBe(false);
|
||||
});
|
||||
|
||||
it('should list all items', async () => {
|
||||
const item1 = await repository.create({ name: 'test1' });
|
||||
const item2 = await repository.create({ name: 'test2' });
|
||||
|
||||
const all = await repository.findAll();
|
||||
expect(all).toHaveLength(2);
|
||||
expect(all).toContainEqual(item1);
|
||||
expect(all).toContainEqual(item2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('InMemoryPrintHistoryRepository', () => {
|
||||
let repository: InMemoryPrintHistoryRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryPrintHistoryRepository();
|
||||
});
|
||||
|
||||
it('should find print history by task id', async () => {
|
||||
const history1 = await repository.create({ task_id: 1, user_id: 1, printed_at: new Date() });
|
||||
const history2 = await repository.create({ task_id: 1, user_id: 2, printed_at: new Date() });
|
||||
await repository.create({ task_id: 2, user_id: 1, printed_at: new Date() });
|
||||
|
||||
const taskHistory = await repository.findByTaskId(1);
|
||||
expect(taskHistory).toHaveLength(2);
|
||||
expect(taskHistory).toContainEqual(history1);
|
||||
expect(taskHistory).toContainEqual(history2);
|
||||
});
|
||||
|
||||
it('should find print history by step id', async () => {
|
||||
const history1 = await repository.create({ step_id: 1, user_id: 1, printed_at: new Date() });
|
||||
const history2 = await repository.create({ step_id: 1, user_id: 2, printed_at: new Date() });
|
||||
await repository.create({ step_id: 2, user_id: 1, printed_at: new Date() });
|
||||
|
||||
const stepHistory = await repository.findByStepId(1);
|
||||
expect(stepHistory).toHaveLength(2);
|
||||
expect(stepHistory).toContainEqual(history1);
|
||||
expect(stepHistory).toContainEqual(history2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('InMemoryStepRepository', () => {
|
||||
let repository: InMemoryStepRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryStepRepository();
|
||||
});
|
||||
|
||||
it('should find steps by task id', async () => {
|
||||
const step1 = await repository.create({ task_id: 1, name: 'Step 1', instructions: 'Test', order: 1, print_count: 0 });
|
||||
const step2 = await repository.create({ task_id: 1, name: 'Step 2', instructions: 'Test', order: 2, print_count: 0 });
|
||||
await repository.create({ task_id: 2, name: 'Step 3', instructions: 'Test', order: 1, print_count: 0 });
|
||||
|
||||
const taskSteps = await repository.findByTaskId(1);
|
||||
expect(taskSteps).toHaveLength(2);
|
||||
expect(taskSteps).toContainEqual(step1);
|
||||
expect(taskSteps).toContainEqual(step2);
|
||||
});
|
||||
|
||||
it('should increment print count', async () => {
|
||||
const step = await repository.create({ task_id: 1, name: 'Step 1', instructions: 'Test', order: 1, print_count: 0 });
|
||||
|
||||
const success = await repository.incrementPrintCount(step.id);
|
||||
expect(success).toBe(true);
|
||||
|
||||
const updated = await repository.findById(step.id);
|
||||
expect(updated?.print_count).toBe(1);
|
||||
expect(updated?.last_printed_at).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it('should return false when incrementing non-existent step', async () => {
|
||||
const success = await repository.incrementPrintCount(999);
|
||||
expect(success).toBe(false);
|
||||
});
|
||||
});
|
76
server/src/db/repositories/__tests__/task-repository.test.ts
Normal file
76
server/src/db/repositories/__tests__/task-repository.test.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { testDb } from '../../__tests__/setup';
|
||||
import { TaskRepository } from '../task-repository';
|
||||
import { Task } from '@shared/index';
|
||||
|
||||
describe('TaskRepository (integration)', () => {
|
||||
let repository: TaskRepository;
|
||||
let groupId: number;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Clean up tasks and groups before each test
|
||||
await testDb('tasks').delete();
|
||||
await testDb('groups').delete();
|
||||
// Insert a group for foreign key
|
||||
[groupId] = await testDb('groups').insert({ name: 'Test Group' });
|
||||
repository = new TaskRepository(testDb);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.destroy();
|
||||
});
|
||||
|
||||
it('should create and find tasks by group id', async () => {
|
||||
const task1: Omit<Task, 'id' | 'created_at' | 'updated_at'> = {
|
||||
name: 'Task 1',
|
||||
group_id: groupId,
|
||||
print_count: 0,
|
||||
last_printed_at: undefined,
|
||||
};
|
||||
const task2: Omit<Task, 'id' | 'created_at' | 'updated_at'> = {
|
||||
name: 'Task 2',
|
||||
group_id: groupId,
|
||||
print_count: 0,
|
||||
last_printed_at: undefined,
|
||||
};
|
||||
await testDb('tasks').insert([task1, task2]);
|
||||
const found = await repository.findByGroupId(groupId);
|
||||
expect(found).toHaveLength(2);
|
||||
expect(found.map(t => t.name)).toEqual(expect.arrayContaining(['Task 1', 'Task 2']));
|
||||
});
|
||||
|
||||
it('should find recent tasks', async () => {
|
||||
const now = new Date();
|
||||
const task1 = { name: 'Recent 1', group_id: groupId, print_count: 0, last_printed_at: undefined, created_at: new Date(now.getTime() - 2000).toISOString(), updated_at: new Date(now.getTime() - 2000).toISOString() };
|
||||
const task2 = { name: 'Recent 2', group_id: groupId, print_count: 0, last_printed_at: undefined, created_at: new Date(now.getTime() - 1000).toISOString(), updated_at: new Date(now.getTime() - 1000).toISOString() };
|
||||
const task3 = { name: 'Recent 3', group_id: groupId, print_count: 0, last_printed_at: undefined, created_at: now.toISOString(), updated_at: now.toISOString() };
|
||||
await testDb('tasks').insert([task1, task2, task3]);
|
||||
const found = await repository.findRecent(2);
|
||||
expect(found).toHaveLength(2);
|
||||
// Should be ordered by created_at desc, so the most recent first
|
||||
expect(found[0].name).toBe('Recent 3');
|
||||
expect(found[1].name).toBe('Recent 2');
|
||||
});
|
||||
|
||||
it('should find frequent tasks', async () => {
|
||||
const task1 = { name: 'Frequent 1', group_id: groupId, print_count: 10, last_printed_at: undefined };
|
||||
const task2 = { name: 'Frequent 2', group_id: groupId, print_count: 5, last_printed_at: undefined };
|
||||
await testDb('tasks').insert([task1, task2]);
|
||||
const found = await repository.findFrequent(1);
|
||||
expect(found).toHaveLength(1);
|
||||
expect(found[0].name).toBe('Frequent 1');
|
||||
});
|
||||
|
||||
it('should increment print count', async () => {
|
||||
const [id] = await testDb('tasks').insert({ name: 'Print Task', group_id: groupId, print_count: 0, last_printed_at: undefined });
|
||||
const result = await repository.incrementPrintCount(id);
|
||||
expect(result).toBe(true);
|
||||
const updated = await testDb('tasks').where({ id }).first();
|
||||
expect(updated.print_count).toBe(1);
|
||||
expect(updated.last_printed_at).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when incrementing non-existent task', async () => {
|
||||
const result = await repository.incrementPrintCount(99999);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export abstract class BaseRepository<T> {
|
||||
export abstract class BaseRepository<T extends { created_at?: Date; updated_at?: Date }> {
|
||||
protected tableName: string;
|
||||
protected db: Knex;
|
||||
|
||||
@ -18,8 +18,16 @@ export abstract class BaseRepository<T> {
|
||||
return result || null;
|
||||
}
|
||||
|
||||
async create(data: Omit<T, 'id' | 'created_at' | 'updated_at'>): Promise<T> {
|
||||
const [id] = await this.db(this.tableName).insert(data);
|
||||
async create(data: Omit<T, 'id' | 'created_at' | 'updated_at'> & Partial<Pick<T, 'created_at' | 'updated_at'>>): Promise<T> {
|
||||
const now = new Date();
|
||||
const created_at = (data.created_at ?? now).toISOString();
|
||||
const updated_at = (data.updated_at ?? now).toISOString();
|
||||
const row = {
|
||||
...data,
|
||||
created_at,
|
||||
updated_at,
|
||||
};
|
||||
const [id] = await this.db(this.tableName).insert(row);
|
||||
return await this.findById(id) as T;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { BaseRepository } from './base-repository';
|
||||
import { PrintHistory, Step } from '@shared/index';
|
||||
|
||||
export class InMemoryRepository<T extends { id: number }> {
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { Knex } from 'knex';
|
||||
import { testDb } from '../../db/__tests__/setup';
|
||||
import { resolvers } from '../resolvers';
|
||||
import { GroupRepository, TaskRepository, StepRepository, UserRepository, NoteRepository, PrintHistoryRepository, ImageRepository } from '../../db/repositories';
|
||||
import type { Printer, Task, Step } from '@shared/index';
|
||||
import type { Printer } from '@shared/index';
|
||||
|
||||
describe('GraphQL Resolvers', () => {
|
||||
const context = {
|
||||
@ -222,6 +221,33 @@ describe('GraphQL Resolvers', () => {
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recentTasks', () => {
|
||||
it('should return recent tasks', async () => {
|
||||
const group = await groupRepo.create({ name: 'Test Group' });
|
||||
const oldTimestamp = new Date('2020-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: 'New Task', group_id: group.id, print_count: 0, created_at: newTimestamp, updated_at: newTimestamp });
|
||||
const result = await resolvers.Query.recentTasks(null, null, context);
|
||||
console.log('result', JSON.stringify(result, null, 2));
|
||||
expect(result.length).toBeGreaterThanOrEqual(2);
|
||||
expect(result[0].name).toBe('New Task');
|
||||
expect(result[1].name).toBe('Old Task');
|
||||
});
|
||||
});
|
||||
|
||||
describe('frequentTasks', () => {
|
||||
it('should return frequent tasks', async () => {
|
||||
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: 'More Frequent', group_id: group.id, print_count: 10 });
|
||||
const result = await resolvers.Query.frequentTasks(null, null, context);
|
||||
expect(result.length).toBeGreaterThanOrEqual(2);
|
||||
expect(result[0].name).toBe('More Frequent');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mutations', () => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Knex } from 'knex';
|
||||
import { GroupRepository, TaskRepository, StepRepository, UserRepository, NoteRepository, PrintHistoryRepository, ImageRepository } from '../db/repositories';
|
||||
import { printTask, printStep } from '../printer/helpers';
|
||||
import type { Printer } from '@shared/index';
|
||||
|
||||
interface Context {
|
||||
@ -148,44 +149,16 @@ export const resolvers = {
|
||||
});
|
||||
},
|
||||
printTask: async (_: any, { id, userId }: { id: string; userId: string }, { db, printer }: Context) => {
|
||||
const taskRepo = new TaskRepository(db);
|
||||
const userRepo = new UserRepository(db);
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
const task = await taskRepo.findById(parseInt(id));
|
||||
if (!task) throw new Error('Task not found');
|
||||
const user = await userRepo.findById(parseInt(userId));
|
||||
if (!user) throw new Error('User not found');
|
||||
await printer.printTask(task, db);
|
||||
await printHistoryRepo.create({
|
||||
user_id: parseInt(userId),
|
||||
task_id: parseInt(id),
|
||||
printed_at: new Date(),
|
||||
});
|
||||
await taskRepo.update(parseInt(id), {
|
||||
print_count: (task.print_count || 0) + 1,
|
||||
last_printed_at: new Date(),
|
||||
});
|
||||
return await taskRepo.findById(parseInt(id));
|
||||
return await printTask(parseInt(id), parseInt(userId), db, printer);
|
||||
},
|
||||
printStep: async (_: any, { id, userId }: { id: string; userId: string }, { db, printer }: Context) => {
|
||||
const stepRepo = new StepRepository(db);
|
||||
const userRepo = new UserRepository(db);
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
const step = await stepRepo.findById(parseInt(id));
|
||||
if (!step) throw new Error('Step not found');
|
||||
const user = await userRepo.findById(parseInt(userId));
|
||||
if (!user) throw new Error('User not found');
|
||||
await printer.printStep(step, db);
|
||||
await printHistoryRepo.create({
|
||||
user_id: parseInt(userId),
|
||||
step_id: parseInt(id),
|
||||
printed_at: new Date(),
|
||||
});
|
||||
await stepRepo.update(parseInt(id), {
|
||||
print_count: (step.print_count || 0) + 1,
|
||||
last_printed_at: new Date(),
|
||||
});
|
||||
return await stepRepo.findById(parseInt(id));
|
||||
return await printStep(parseInt(id), parseInt(userId), db, printer);
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { resolvers } from './graphql/resolvers';
|
||||
import { createDb } from './db';
|
||||
import logger from './logger';
|
||||
import { createPrinter } from './printer';
|
||||
import { PrintHistoryRepository, StepRepository } from './db/repositories';
|
||||
import cors from 'cors';
|
||||
|
||||
const app = express();
|
||||
@ -14,7 +15,9 @@ const port = process.env.PORT || 4000;
|
||||
|
||||
async function startServer() {
|
||||
const db = createDb();
|
||||
const printer = createPrinter();
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
const stepRepo = new StepRepository(db);
|
||||
const printer = createPrinter(printHistoryRepo, stepRepo);
|
||||
|
||||
const server = new ApolloServer({
|
||||
typeDefs,
|
||||
|
61
server/src/printer/helpers.ts
Normal file
61
server/src/printer/helpers.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Printer, Task, Step } from '@shared/index';
|
||||
import { TaskRepository, StepRepository, PrintHistoryRepository } from '../db/repositories';
|
||||
|
||||
export async function printTask(
|
||||
taskId: number,
|
||||
userId: number,
|
||||
db: Knex,
|
||||
printer: Printer
|
||||
): Promise<Task> {
|
||||
const taskRepo = new TaskRepository(db);
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
|
||||
const task = await taskRepo.findById(taskId);
|
||||
if (!task) throw new Error('Task not found');
|
||||
|
||||
await printer.printTask(task, db);
|
||||
await printHistoryRepo.create({
|
||||
user_id: userId,
|
||||
task_id: taskId,
|
||||
printed_at: new Date(),
|
||||
});
|
||||
|
||||
await taskRepo.update(taskId, {
|
||||
print_count: (task.print_count || 0) + 1,
|
||||
last_printed_at: new Date(),
|
||||
});
|
||||
|
||||
const updatedTask = await taskRepo.findById(taskId);
|
||||
if (!updatedTask) throw new Error('Task not found after update');
|
||||
return updatedTask;
|
||||
}
|
||||
|
||||
export async function printStep(
|
||||
stepId: number,
|
||||
userId: number,
|
||||
db: Knex,
|
||||
printer: Printer
|
||||
): Promise<Step> {
|
||||
const stepRepo = new StepRepository(db);
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
|
||||
const step = await stepRepo.findById(stepId);
|
||||
if (!step) throw new Error('Step not found');
|
||||
|
||||
await printer.printStep(step, db);
|
||||
await printHistoryRepo.create({
|
||||
user_id: userId,
|
||||
step_id: stepId,
|
||||
printed_at: new Date(),
|
||||
});
|
||||
|
||||
await stepRepo.update(stepId, {
|
||||
print_count: (step.print_count || 0) + 1,
|
||||
last_printed_at: new Date(),
|
||||
});
|
||||
|
||||
const updatedStep = await stepRepo.findById(stepId);
|
||||
if (!updatedStep) throw new Error('Step not found after update');
|
||||
return updatedStep;
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Task, Step, Printer } from '@shared/index';
|
||||
import { Printer } from '@shared/index';
|
||||
import { TestPrinter } from './test-printer';
|
||||
import { SerialPrinter } from './serial-printer';
|
||||
import logger from '../logger';
|
||||
@ -15,19 +14,5 @@ export function createPrinter(printHistoryRepo: PrintHistoryRepository, stepRepo
|
||||
}
|
||||
}
|
||||
|
||||
export async function printTask(task: Task, db: Knex): Promise<void> {
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
const stepRepo = new StepRepository(db);
|
||||
const printer = createPrinter(printHistoryRepo, stepRepo);
|
||||
await printer.printTask(task, db);
|
||||
}
|
||||
|
||||
export async function printStep(step: Step, db: Knex): Promise<void> {
|
||||
const printHistoryRepo = new PrintHistoryRepository(db);
|
||||
const stepRepo = new StepRepository(db);
|
||||
const printer = createPrinter(printHistoryRepo, stepRepo);
|
||||
await printer.printStep(step, db);
|
||||
}
|
||||
|
||||
export { SerialPrinter } from './serial-printer';
|
||||
export { TestPrinter } from './test-printer';
|
Loading…
Reference in New Issue
Block a user