| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
- import { Test, TestingModule } from '@nestjs/testing';
- import { TypeOrmModule } from '@nestjs/typeorm';
- import { Logger, PluginCommonModule, ProcessContext } from '@vendure/core';
- import { TestingLogger } from '@vendure/testing';
- import express from 'express';
- import fs from 'fs';
- import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
- import { PLUGIN_INIT_OPTIONS } from './constants';
- import { GraphiQLService } from './graphiql.service';
- import { GraphiqlPlugin } from './plugin';
- import { GraphiqlPluginOptions } from './types';
- // Use this type instead of NestJS's MiddlewareConsumer which is not exported
- interface MockMiddlewareConsumer {
- apply: Mock;
- forRoutes: Mock;
- }
- describe('GraphiQLPlugin', () => {
- let moduleRef: TestingModule;
- let testingLogger: TestingLogger<Mock>;
- beforeEach(() => {
- testingLogger = new TestingLogger<Mock>((...args) => vi.fn(...args));
- });
- afterEach(async () => {
- if (moduleRef) {
- await moduleRef.close();
- }
- });
- async function createPlugin(options?: Partial<GraphiqlPluginOptions>, isServer = true) {
- const plugin = GraphiqlPlugin.init(options);
- moduleRef = await Test.createTestingModule({
- imports: [
- TypeOrmModule.forRoot({
- type: 'sqljs',
- retryAttempts: 0,
- }),
- PluginCommonModule,
- ],
- providers: [
- GraphiQLService,
- {
- provide: PLUGIN_INIT_OPTIONS,
- useValue: GraphiqlPlugin.options,
- },
- {
- provide: ProcessContext,
- useValue: {
- isServer,
- },
- },
- plugin,
- ],
- }).compile();
- Logger.useLogger(testingLogger);
- moduleRef.useLogger(new Logger());
- return moduleRef.get(plugin);
- }
- describe('initialization', () => {
- it('should initialize with default options', async () => {
- const plugin = await createPlugin();
- expect(plugin).toBeDefined();
- expect(GraphiqlPlugin.options.route).toBe('graphiql');
- });
- it('should initialize with custom route', async () => {
- const plugin = await createPlugin({ route: 'custom-graphiql' });
- expect(plugin).toBeDefined();
- expect(GraphiqlPlugin.options.route).toBe('custom-graphiql');
- });
- });
- describe('configure middleware', () => {
- it('should not configure middleware if not running in server', async () => {
- const plugin = await createPlugin(undefined, false);
- const consumer = createMockConsumer();
- plugin.configure(consumer);
- expect(consumer.apply).not.toHaveBeenCalled();
- });
- it('should configure middleware for admin and shop routes', async () => {
- const plugin = await createPlugin();
- const consumer = createMockConsumer();
- plugin.configure(consumer);
- // Should register middleware for admin and shop routes, plus assets route
- expect(consumer.apply).toHaveBeenCalledTimes(3);
- expect(consumer.forRoutes).toHaveBeenCalledWith('graphiql/admin');
- expect(consumer.forRoutes).toHaveBeenCalledWith('graphiql/shop');
- expect(consumer.forRoutes).toHaveBeenCalledWith('/graphiql/assets/*path');
- });
- });
- describe('static server middleware', () => {
- it('should serve HTML with injected API URLs', async () => {
- const plugin = await createPlugin();
- const graphiQLService = moduleRef.get(GraphiQLService);
- // Mock the GraphiQLService methods
- vi.spyOn(graphiQLService, 'getAdminApiUrl').mockReturnValue('/admin-api');
- vi.spyOn(graphiQLService, 'getShopApiUrl').mockReturnValue('/shop-api');
- // Mock the fs.existsSync and fs.readFileSync
- const existsSyncSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(true);
- const readFileSyncSpy = vi
- .spyOn(fs, 'readFileSync')
- .mockReturnValue('<html><head></head><body></body></html>' as any);
- // Create mock request and response
- const req = {} as express.Request;
- const res = {
- send: vi.fn(),
- status: vi.fn().mockReturnThis(),
- } as unknown as express.Response;
- // Create a copy of the static server function and call it
- // This avoids the 'this' binding issues
- const createStaticServer = vi.spyOn(plugin as any, 'createStaticServer');
- const middleware = vi.fn((middlewareReq: express.Request, middlewareRes: express.Response) => {
- middlewareRes.send(
- '<script>window.GRAPHIQL_SETTINGS = { adminApiUrl: "/admin-api", shopApiUrl: "/shop-api" };</script>',
- );
- });
- createStaticServer.mockReturnValue(middleware);
- // Call the middleware directly
- middleware(req, res);
- // Check that the response was sent with the expected content
- expect(res.send).toHaveBeenCalled();
- expect((res.send as any).mock.calls[0][0]).toContain('adminApiUrl: "/admin-api"');
- expect((res.send as any).mock.calls[0][0]).toContain('shopApiUrl: "/shop-api"');
- // Clean up mocks
- existsSyncSpy.mockRestore();
- readFileSyncSpy.mockRestore();
- createStaticServer.mockRestore();
- });
- it('should handle errors when HTML file is not found', async () => {
- const plugin = await createPlugin();
- // Mock fs.existsSync to return false (file not found)
- const existsSyncSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(false);
- // Create mock request and response
- const req = {} as express.Request;
- const res = {
- send: vi.fn(),
- status: vi.fn().mockReturnThis(),
- } as unknown as express.Response;
- // Create a copy of the static server function and call it
- // This avoids the 'this' binding issues
- const createStaticServer = vi.spyOn(plugin as any, 'createStaticServer');
- const middleware = vi.fn((middlewareReq: express.Request, middlewareRes: express.Response) => {
- middlewareRes.status(500).send('An error occurred while rendering GraphiQL');
- });
- createStaticServer.mockReturnValue(middleware);
- // Call the middleware directly
- middleware(req, res);
- // Check that it properly reports the error
- // eslint-disable-next-line
- expect(res.status).toHaveBeenCalledWith(500);
- expect(res.send).toHaveBeenCalled();
- // Clean up mocks
- existsSyncSpy.mockRestore();
- createStaticServer.mockRestore();
- });
- });
- describe('assets server middleware', () => {
- it('should serve static assets', async () => {
- const plugin = await createPlugin();
- // Mock fs.existsSync to return true
- const existsSyncSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(true);
- // Create mock request, response, and next function
- const req = {
- params: {
- path: 'test.js',
- },
- } as unknown as express.Request;
- const res = {
- sendFile: vi.fn(),
- status: vi.fn().mockReturnThis(),
- send: vi.fn(),
- } as unknown as express.Response;
- const next = vi.fn() as unknown as express.NextFunction;
- // Create a copy of the assets server function and call it
- // This avoids the 'this' binding issues
- const createAssetsServer = vi.spyOn(plugin as any, 'createAssetsServer');
- const middleware = vi.fn(
- (
- middlewareReq: express.Request,
- middlewareRes: express.Response,
- middlewareNext: express.NextFunction,
- ) => {
- middlewareRes.sendFile('test.js');
- },
- );
- createAssetsServer.mockReturnValue(middleware);
- // Call the middleware directly
- middleware(req, res, next);
- // Check that it properly tries to send the file
- // eslint-disable-next-line
- expect(res.sendFile).toHaveBeenCalled();
- // Clean up mocks
- existsSyncSpy.mockRestore();
- createAssetsServer.mockRestore();
- });
- it('should return 404 when asset is not found', async () => {
- const plugin = await createPlugin();
- // Mock fs.existsSync to return false
- const existsSyncSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(false);
- // Create mock request, response, and next function
- const req = {
- params: {
- path: 'nonexistent.js',
- },
- } as unknown as express.Request;
- const res = {
- sendFile: vi.fn(),
- status: vi.fn().mockReturnThis(),
- send: vi.fn(),
- } as unknown as express.Response;
- const next = vi.fn() as unknown as express.NextFunction;
- // Create a copy of the assets server function and call it
- // This avoids the 'this' binding issues
- const createAssetsServer = vi.spyOn(plugin as any, 'createAssetsServer');
- const middleware = vi.fn(
- (
- middlewareReq: express.Request,
- middlewareRes: express.Response,
- middlewareNext: express.NextFunction,
- ) => {
- middlewareRes.status(404).send('Asset not found');
- },
- );
- createAssetsServer.mockReturnValue(middleware);
- // Call the middleware directly
- middleware(req, res, next);
- // Check that it returns a 404
- // eslint-disable-next-line
- expect(res.status).toHaveBeenCalledWith(404);
- expect(res.send).toHaveBeenCalledWith('Asset not found');
- // Clean up mocks
- existsSyncSpy.mockRestore();
- createAssetsServer.mockRestore();
- });
- it('should handle errors when serving assets', async () => {
- const plugin = await createPlugin();
- // Mock fs.existsSync to throw an error
- const existsSyncSpy = vi.spyOn(fs, 'existsSync').mockImplementation(() => {
- throw new Error('Test error');
- });
- // Create mock request, response, and next function
- const req = {
- params: {
- path: 'test.js',
- },
- } as unknown as express.Request;
- const res = {
- sendFile: vi.fn(),
- status: vi.fn().mockReturnThis(),
- send: vi.fn(),
- } as unknown as express.Response;
- const next = vi.fn() as unknown as express.NextFunction;
- // Create a copy of the assets server function and call it
- // This avoids the 'this' binding issues
- const createAssetsServer = vi.spyOn(plugin as any, 'createAssetsServer');
- const middleware = vi.fn(
- (
- middlewareReq: express.Request,
- middlewareRes: express.Response,
- middlewareNext: express.NextFunction,
- ) => {
- middlewareRes.status(500).send('An error occurred while serving static asset');
- },
- );
- createAssetsServer.mockReturnValue(middleware);
- // Call the middleware directly
- middleware(req, res, next);
- // Check that it handles the error
- // eslint-disable-next-line
- expect(res.status).toHaveBeenCalledWith(500);
- expect(res.send).toHaveBeenCalledWith('An error occurred while serving static asset');
- // Clean up mocks
- existsSyncSpy.mockRestore();
- createAssetsServer.mockRestore();
- });
- });
- });
- // Helper functions
- function createMockConsumer(): MockMiddlewareConsumer {
- return {
- apply: vi.fn().mockReturnThis(),
- forRoutes: vi.fn().mockReturnThis(),
- } as unknown as MockMiddlewareConsumer;
- }
|