fastlane
Version:
Fastlane is a fast and flexible API framework for Node.js. It automatically creates Express routes from your project's file structure, making it easy to build APIs quickly and efficiently.
224 lines • 9.25 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const attach_1 = require("../attach");
const handler = __importStar(require("../handler"));
// Create a spy for Router
const mockRouterFn = jest.fn();
const mockRouter = {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
delete: jest.fn()
};
// Mock fs with a properly implemented readdir function
jest.mock('fs', () => {
return {
promises: {
readdir: jest.fn().mockImplementation(() => Promise.resolve([]))
}
};
});
// Get a reference to the mocked function for use in tests
const fsPromises = jest.requireMock('fs').promises;
// Mock dependencies
jest.mock('express', () => {
return {
Router: () => {
mockRouterFn();
return mockRouter;
}
};
});
// Mock the methodHandler to properly wrap the handler functions
jest.mock('../handler', () => ({
methodHandler: jest.fn().mockImplementation((fn) => {
// Return a function that can be used as a route handler
const mockHandler = function () {
if (fn)
return fn();
return undefined;
};
return mockHandler;
})
}));
describe('attachRoutes', () => {
let mockMethodHandler;
let consoleSpy;
beforeEach(() => {
jest.resetAllMocks();
mockMethodHandler = handler.methodHandler;
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
});
afterEach(() => {
consoleSpy.mockRestore();
});
it('should return a router instance', async () => {
// Set up the mock to resolve immediately
fsPromises.readdir.mockResolvedValue([]);
const router = (0, attach_1.attachRoutes)('./routes');
// Wait for any promises to resolve
await new Promise(process.nextTick);
expect(mockRouterFn).toHaveBeenCalled();
expect(router).toBeDefined();
});
it('should scan the directory for route files', async () => {
fsPromises.readdir.mockResolvedValue([]);
(0, attach_1.attachRoutes)('./routes');
// Wait for promises to resolve
await new Promise(process.nextTick);
expect(fsPromises.readdir).toHaveBeenCalledWith('./routes', { recursive: true });
});
it('should attach route handlers for matching files', async () => {
// Mock file system with route files
fsPromises.readdir.mockResolvedValue(['users/route.ts', 'products/route.js']);
// Create mock handler functions
const getUserHandler = jest.fn();
const postUserHandler = jest.fn();
const putProductHandler = jest.fn();
const deleteProductHandler = jest.fn();
// Mock user routes
const mockUserRoutes = {
GET: getUserHandler,
POST: postUserHandler
};
// Mock product routes
const mockProductRoutes = {
default: {
PUT: putProductHandler,
DELETE: deleteProductHandler
}
};
// Mock the methodHandler to return the wrapped handler
mockMethodHandler.mockImplementation((fn) => {
return fn;
});
// Mock the dynamic import
const importFn = jest.fn().mockImplementation((path) => {
if (path.includes('users/route.ts'))
return Promise.resolve(mockUserRoutes);
if (path.includes('products/route.js'))
return Promise.resolve(mockProductRoutes);
return Promise.resolve({});
});
(0, attach_1.attachRoutes)('./routes', importFn);
// Wait for promises to resolve
await new Promise(process.nextTick);
// Verify routes were attached
expect(mockRouter.get).toHaveBeenCalledWith('/users', expect.any(Function));
expect(mockRouter.post).toHaveBeenCalledWith('/users', expect.any(Function));
expect(mockRouter.put).toHaveBeenCalledWith('/products', expect.any(Function));
expect(mockRouter.delete).toHaveBeenCalledWith('/products', expect.any(Function));
// Verify console logs
expect(consoleSpy).toHaveBeenCalledWith('Attaching GET /users');
expect(consoleSpy).toHaveBeenCalledWith('Attaching POST /users');
expect(consoleSpy).toHaveBeenCalledWith('Attaching PUT /products');
expect(consoleSpy).toHaveBeenCalledWith('Attaching DELETE /products');
});
it('should handle files that do not match the route pattern', async () => {
fsPromises.readdir.mockResolvedValue(['users/index.ts', 'products/route.ts']);
const mockProductRoutes = {
GET: jest.fn()
};
// Mock the dynamic import
const importFn = jest.fn((path) => {
if (path.includes('products/route.ts'))
return Promise.resolve(mockProductRoutes);
return Promise.resolve({});
});
// Don't override the methodHandler implementation
mockMethodHandler.mockImplementation((fn) => fn);
(0, attach_1.attachRoutes)('./routes', importFn);
// Wait for promises to resolve
await new Promise(process.nextTick);
// Only the route file should be processed
expect(mockRouter.get).toHaveBeenCalledWith('/products', expect.any(Function));
expect(mockRouter.get).toHaveBeenCalledTimes(1);
});
it('should correctly parse the URL path from the file path', async () => {
fsPromises.readdir.mockResolvedValue(['users/admin/route.ts', 'api/v1/products/route.js']);
const mockUserAdminRoutes = {
GET: jest.fn()
};
const mockProductsV1Routes = {
POST: jest.fn()
};
// Mock the dynamic import
const importFn = jest.fn((path) => {
if (path.includes('users/admin/route.ts'))
return Promise.resolve(mockUserAdminRoutes);
if (path.includes('api/v1/products/route.js'))
return Promise.resolve(mockProductsV1Routes);
return Promise.resolve({});
});
// Don't override the methodHandler implementation
mockMethodHandler.mockImplementation((fn) => fn);
(0, attach_1.attachRoutes)('./routes', importFn);
// Wait for promises to resolve
await new Promise(process.nextTick);
// Verify the paths are correctly parsed
expect(mockRouter.get).toHaveBeenCalledWith('/users/admin', expect.any(Function));
expect(mockRouter.post).toHaveBeenCalledWith('/api/v1/products', expect.any(Function));
});
it('should handle both default and direct exports', async () => {
fsPromises.readdir.mockResolvedValue(['direct-export/route.ts', 'default-export/route.ts']);
const mockDirectExport = {
GET: jest.fn()
};
const mockDefaultExport = {
default: {
POST: jest.fn()
}
};
// Mock the dynamic import
const importFn = jest.fn((path) => {
if (path.includes('direct-export/route.ts'))
return Promise.resolve(mockDirectExport);
if (path.includes('default-export/route.ts'))
return Promise.resolve(mockDefaultExport);
return Promise.resolve({});
});
// Don't override the methodHandler implementation
mockMethodHandler.mockImplementation((fn) => fn);
(0, attach_1.attachRoutes)('./routes', importFn);
// Wait for promises to resolve
await new Promise(process.nextTick);
// Verify both export styles work
expect(mockRouter.get).toHaveBeenCalledWith('/direct-export', expect.any(Function));
expect(mockRouter.post).toHaveBeenCalledWith('/default-export', expect.any(Function));
});
});
//# sourceMappingURL=attach.test.js.map