Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions codewit/api/src/controllers/module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
Demo,
Language,
Module,
ModuleResources,
Resource,
sequelize,
} from '../models';
import { createModule, getModule, updateModule } from './module';

jest.mock('../models', () => ({
Demo: { findAll: jest.fn() },
Language: { findOrCreate: jest.fn() },
Module: {
associations: { resources: 'resources' },
create: jest.fn(),
findByPk: jest.fn(),
},
ModuleResources: {
bulkCreate: jest.fn(),
destroy: jest.fn(),
},
Resource: {},
sequelize: {
transaction: jest.fn(async (
callback: (transaction: object) => Promise<unknown>,
) => callback({})),
},
}));

const resource = (uid: number) => ({
get: () => ({
uid,
title: `Resource ${uid}`,
url: `https://example.com/${uid}`,
source: 'Example',
likes: 0,
}),
});

const makeModule = () => ({
uid: 12,
topic: 'operation',
language: { name: 'cpp' },
demos: [],
resources: [resource(3), resource(1)],
setDemos: jest.fn(),
setLanguage: jest.fn(),
reload: jest.fn(),
save: jest.fn(),
});

describe('module resource ordering', () => {
const transaction = {};

beforeEach(() => {
jest.clearAllMocks();
jest.mocked(Language.findOrCreate).mockResolvedValue([{ uid: 4 }] as never);
jest.mocked(Demo.findAll).mockResolvedValue([]);
});

it('persists submitted resource order when creating a module', async () => {
const module = makeModule();
jest.mocked(Module.create).mockResolvedValue(module as never);

const created = await createModule('operation', 'cpp', [3, 1]);

expect(ModuleResources.bulkCreate).toHaveBeenCalledWith(
[
{ moduleUid: 12, resourceUid: 3, ordering: 0 },
{ moduleUid: 12, resourceUid: 1, ordering: 1 },
],
{ transaction },
);
expect(module.reload).toHaveBeenCalledWith(expect.objectContaining({
order: [['resources', ModuleResources, 'ordering', 'ASC']],
}));
expect(created.resources.map(({ uid }) => uid)).toEqual([3, 1]);
});

it('replaces persisted resource order when updating a module', async () => {
const module = makeModule();
jest.mocked(Module.findByPk).mockResolvedValue(module as never);

const updated = await updateModule(12, undefined, undefined, [3, 1]);

expect(ModuleResources.destroy).toHaveBeenCalledWith({
where: { moduleUid: 12 },
transaction,
});
expect(ModuleResources.bulkCreate).toHaveBeenCalledWith(
[
{ moduleUid: 12, resourceUid: 3, ordering: 0 },
{ moduleUid: 12, resourceUid: 1, ordering: 1 },
],
{ transaction },
);
expect(updated.resources.map(({ uid }) => uid)).toEqual([3, 1]);
});

it('requests persisted resource order when retrieving a module', async () => {
const module = makeModule();
jest.mocked(Module.findByPk).mockResolvedValue(module as never);

const retrieved = await getModule(12);

expect(Module.findByPk).toHaveBeenCalledWith(12, expect.objectContaining({
order: [['resources', ModuleResources, 'ordering', 'ASC']],
}));
expect(retrieved?.resources.map(({ uid }) => uid)).toEqual([3, 1]);
});
});
85 changes: 85 additions & 0 deletions codewit/api/src/routes/demo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import 'passport';
import { Attempt, Demo, DemoExercises } from '../models';
import { getDemoAttempt } from './demo';

jest.mock('../models', () => ({
Attempt: { findAll: jest.fn() },
Demo: {
associations: { exercises: 'exercises' },
findByPk: jest.fn(),
},
DemoExercises: 'DemoExercises',
DemoTags: 'DemoTags',
Language: 'Language',
Tag: 'Tag',
UserExerciseCompletion: 'UserExerciseCompletion',
User: {},
sequelize: { query: jest.fn(), transaction: jest.fn() },
}));

describe('GET /demos/:uid/attempt exercise ordering', () => {
it('returns exercises in their persisted DemoExercises order', async () => {
jest.mocked(Demo.findByPk).mockImplementation(async (_uid, options) => {
const order = options?.order as unknown[];
const hasExerciseOrder = order.some(entry =>
Array.isArray(entry)
&& entry[0] === 'exercises'
&& entry[1] === DemoExercises
&& entry[2] === 'order'
&& entry[3] === 'ASC'
);
const exercises = [
{
uid: 1,
prompt: 'First',
language: { name: 'cpp' },
starterCode: '',
DemoExercises: { order: 1 },
},
{
uid: 2,
prompt: 'Second',
language: { name: 'cpp' },
starterCode: '',
DemoExercises: { order: 0 },
},
];

return {
uid: 9,
title: 'Ordered demo',
topic: 'operation',
language: { name: 'cpp' },
youtube_id: 'video',
youtube_thumbnail: 'thumbnail',
tags: [],
exercises: hasExerciseOrder
? exercises.sort((left, right) =>
left.DemoExercises.order - right.DemoExercises.order
)
: exercises,
hasLikedBy: jest.fn().mockResolvedValue(false),
} as never;
});
jest.mocked(Attempt.findAll).mockResolvedValue([]);
const json = jest.fn();
const response = {
json,
status: jest.fn().mockReturnThis(),
};

await getDemoAttempt(
{
params: { uid: '9' },
query: {},
user: { uid: 4 },
} as never,
response as never,
jest.fn(),
);

expect(json.mock.calls[0][0].demo.exercises.map(
(exercise: { uid: number }) => exercise.uid
)).toEqual([2, 1]);
});
});
6 changes: 4 additions & 2 deletions codewit/api/src/routes/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function parse_non_zero_int(given: string): number | null {
return parsed;
}

demoRouter.get("/:uid/attempt", asyncHandle(async (req, res) => {
export const getDemoAttempt = asyncHandle(async (req, res) => {
let maybe_module_id = typeof req.query.module_id === "string" ?
parse_non_zero_int(req.query.module_id) :
null;
Expand Down Expand Up @@ -244,7 +244,9 @@ demoRouter.get("/:uid/attempt", asyncHandle(async (req, res) => {
resources,
related_demos,
} as DemoAttempt);
}));
});

demoRouter.get("/:uid/attempt", getDemoAttempt);

demoRouter.post('/', checkAdmin, async (req, res) => {
try {
Expand Down
Loading
Loading