Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support https for mock util plugin #13700

Merged
merged 10 commits into from
Apr 24, 2024
2 changes: 2 additions & 0 deletions .eslint-dictionary.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@
"junit",
"jwks",
"keyless",
"keyout",
"keyphrase",
"kinesis",
"lang",
Expand Down Expand Up @@ -278,6 +279,7 @@
"openid",
"openpgp",
"opensearch",
"openssl",
"orgs",
"Parti",
"parens",
Expand Down
6 changes: 6 additions & 0 deletions packages/amplify-appsync-simulator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export class AmplifyAppSyncSimulator {
// (undocumented)
init(config: AmplifyAppSyncSimulatorConfig): void;
// (undocumented)
get isHttps(): boolean;
// (undocumented)
get localhostUrl(): string;
// (undocumented)
get pubsub(): PubSub;
Expand Down Expand Up @@ -268,6 +270,10 @@ export type AppSyncSimulatorSchemaConfig = AppSyncMockFile;
export type AppSyncSimulatorServerConfig = {
port?: number;
wsPort?: number;
httpsConfig?: {
sslKeyPath: string;
sslCertPath: string;
};
};

// @public (undocumented)
Expand Down
3 changes: 3 additions & 0 deletions packages/amplify-appsync-simulator/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export class AmplifyAppSyncSimulator {
get localhostUrl(): string {
return this._server.localhostUrl.graphql;
}
get isHttps(): boolean {
return this._server.isHttps;
}
get config(): AmplifyAppSyncSimulatorConfig {
return this._config;
}
Expand Down
32 changes: 29 additions & 3 deletions packages/amplify-appsync-simulator/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { OperationServer } from './operations';
import { AmplifyAppSyncSimulator } from '..';
import { AppSyncSimulatorServerConfig } from '../type-definition';
import { Server, createServer } from 'http';
import { createServer as createHttpsServer } from 'https';
import { readFileSync } from 'fs';
import { fromEvent } from 'promise-toolbox';
import { address as getLocalIpAddress } from 'ip';
import { AppSyncSimulatorSubscriptionServer } from './websocket-subscription';
Expand All @@ -17,10 +19,30 @@ export class AppSyncSimulatorServer {
private _realTimeSubscriptionServer: AppSyncSimulatorSubscriptionServer;
private _url: string;
private _localhostUrl: string;
private _isHttps = false;

constructor(private config: AppSyncSimulatorServerConfig, private simulatorContext: AmplifyAppSyncSimulator) {
this._operationServer = new OperationServer(config, simulatorContext);
this._httpServer = createServer(this._operationServer.app);

// Check if the https configuration is not provided
if (!config.httpsConfig) {
this._httpServer = createServer(this._operationServer.app);
} else {
try {
// Read the ssl cert and key
const sslOptions = {
key: readFileSync(config.httpsConfig.sslKeyPath),
cert: readFileSync(config.httpsConfig.sslCertPath),
};
// Set the isHttps flag to true
this._isHttps = true;
// Create the https server
this._httpServer = createHttpsServer(sslOptions, this._operationServer.app);
} catch (e) {
throw new Error(`SSL key and certificate path provided are invalid. ${e.message}`);
}
}

this._realTimeSubscriptionServer = new AppSyncSimulatorSubscriptionServer(
simulatorContext,
this._httpServer,
Expand Down Expand Up @@ -49,8 +71,9 @@ export class AppSyncSimulatorServer {

this._httpServer.listen(port);
await fromEvent(this._httpServer, 'listening').then(() => {
this._url = `http://${getLocalIpAddress()}:${port}`;
this._localhostUrl = `http://localhost:${port}`;
const protocol = this._isHttps ? 'https' : 'http';
this._url = `${protocol}://${getLocalIpAddress()}:${port}`;
this._localhostUrl = `${protocol}://localhost:${port}`;
});
}

Expand All @@ -68,4 +91,7 @@ export class AppSyncSimulatorServer {
graphql: this._localhostUrl,
};
}
get isHttps() {
return this._isHttps;
}
}
4 changes: 4 additions & 0 deletions packages/amplify-appsync-simulator/src/type-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ export type AmplifyAppSyncSimulatorConfig = {
export type AppSyncSimulatorServerConfig = {
port?: number;
wsPort?: number;
httpsConfig?: {
sslKeyPath: string;
sslCertPath: string;
};
};

export type AmplifyAppSyncSimulatorRequestContext = {
Expand Down
2 changes: 1 addition & 1 deletion packages/amplify-graphiql-explorer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ This is the package that contains graphiql explorer assets for amplify-appsync-s

## Development Mode

When making changes to grapiql-explore, run `yarn start`. All the requests get proxied to `http://localhost:20002/`
When making changes to grapiql-explorer, run `yarn start`. All the requests get proxied to `http://localhost:20002/` by default (If you use the --https flag on `amplify mock`, change the proxy from `http://localhost:20002/` to `https://localhost:20002/` in package.json.)
29 changes: 29 additions & 0 deletions packages/amplify-util-mock/src/__tests__/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,35 @@ describe('Test Mock API methods', () => {
await expect(mockContext.print.error).toHaveBeenCalledWith('Failed to start API Mocking.');
});

it('shows error message and resolution when https enabled if SSL key and certificate paths are not provided', async () => {
ConfigOverrideManager.getInstance = jest.fn().mockReturnValue(jest.fn);
const mockContext = {
print: {
red: jest.fn(),
green: jest.fn(),
error: jest.fn(),
},
parameters: {
options: {
help: false,
},
},
input: {
argv: ['--https'],
},
amplify: {
getEnvInfo: jest.fn().mockReturnValue({ projectPath: mockProjectRoot }),
pathManager: {
getGitIgnoreFilePath: jest.fn(),
},
},
} as unknown as $TSContext;
await run(mockContext);
await expect(mockContext.print.error).toHaveBeenCalledWith(
'\nThe --https option must be followed by the path to the SSL key and the path to the SSL certificate.\n',
);
});

it('attempts to set custom port correctly', async () => {
const GRAPHQL_PORT = 8081;
const mockContext = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getHttpsConfig } from '../utils/get-https-config';

describe('getHttpsConfig', () => {
let context;

beforeEach(() => {
context = {
input: {
argv: [],
},
print: {
error: jest.fn(),
},
};
});

it('returns paths when --https option is followed by key and cert paths', () => {
context.input.argv = ['--https', '/path/to/key', '/path/to/cert'];

const config = getHttpsConfig(context);

expect(config).toEqual({
sslKeyPath: '/path/to/key',
sslCertPath: '/path/to/cert',
});
});

it('returns null and prints error when --https option is not followed by key and cert paths', () => {
context.input.argv = ['--https'];

const config = getHttpsConfig(context);

expect(config).toEqual(null);
expect(context.print.error).toHaveBeenCalled();
});
});
8 changes: 7 additions & 1 deletion packages/amplify-util-mock/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ export class APITest {
private userOverriddenSlots: string[] = [];
private searchableTables: string[] = [];

async start(context, port: number = MOCK_API_PORT, wsPort: number = MOCK_API_PORT) {
async start(
context,
port: number = MOCK_API_PORT,
wsPort: number = MOCK_API_PORT,
httpsConfig?: { sslKeyPath: string; sslCertPath: string },
) {
try {
context.amplify.addCleanUpTask(async (context) => {
await this.stop(context);
Expand All @@ -72,6 +77,7 @@ export class APITest {
this.appSyncSimulator = new AmplifyAppSyncSimulator({
port,
wsPort,
httpsConfig: httpsConfig,
});
await this.appSyncSimulator.start();
await this.resolverOverrideManager.start();
Expand Down
5 changes: 4 additions & 1 deletion packages/amplify-util-mock/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { APITest } from './api';
import { addMockDataToGitIgnore, addMockAPIResourcesToGitIgnore } from '../utils';
import { getMockConfig } from '../utils/mock-config-file';
import { getHttpsConfig } from '../utils/get-https-config';

export async function start(context) {
const testApi = new APITest();
try {
addMockDataToGitIgnore(context);
addMockAPIResourcesToGitIgnore(context);
const mockConfig = await getMockConfig(context);
await testApi.start(context, mockConfig.graphqlPort, mockConfig.graphqlPort);
const httpsConfig = getHttpsConfig(context);

await testApi.start(context, mockConfig.graphqlPort, mockConfig.graphqlPort, httpsConfig);
} catch (e) {
console.log(e);
// Sending term signal so we clean up after ourselves
Expand Down
29 changes: 29 additions & 0 deletions packages/amplify-util-mock/src/utils/get-https-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export function getHttpsConfig(context): { sslKeyPath: string; sslCertPath: string } | null {
if (!context.input || !context.input.argv) {
return null;
}

const argv = context.input.argv;
const httpsIndex = argv.indexOf('--https');

if (httpsIndex !== -1) {
if (httpsIndex < argv.length - 2) {
const keyPath = argv[httpsIndex + 1];
const certPath = argv[httpsIndex + 2];
if (typeof keyPath === 'string' && typeof certPath === 'string') {
return { sslKeyPath: keyPath, sslCertPath: certPath };
} else {
context.print.error('\nThe provided paths for the SSL key and certificate are not valid.\n');
context.print.error('Please ensure you have entered the correct paths.\n');
}
} else {
context.print.error('\nThe --https option must be followed by the path to the SSL key and the path to the SSL certificate.\n');
context.print.error('Example: amplify mock api --https /path/to/key /path/to/cert\n');
context.print.error('In order to generate a key and certificate, you can use openssl:\n');
context.print.error('openssl req -nodes -new -x509 -keyout server.key -out server.cert\n');
afnx marked this conversation as resolved.
Show resolved Hide resolved
context.print.error('Then, run the command again with the paths to the generated key and certificate.\n');
}
}

return null;
}
Loading