Skip to content

Commit 07940f2

Browse files
chore: add tests for studio and preview commands. (#1824)
Co-authored-by: Tushar Anand <tusharannand@gmail.com> Co-authored-by: Shurtu-gal <ashishpadhy1729@gmail.com>
1 parent 86881c1 commit 07940f2

File tree

12 files changed

+795
-726
lines changed

12 files changed

+795
-726
lines changed

package-lock.json

Lines changed: 657 additions & 715 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"mocha": "^10.2.0",
9292
"nodemon": "^3.1.10",
9393
"nyc": "^15.1.0",
94+
"puppeteer": "^24.17.0",
9495
"rimraf": "^3.0.2",
9596
"simple-git": "^3.16.0",
9697
"supertest": "^7.1.4",

src/apps/cli/commands/start/preview.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default class PreviewStudio extends Command {
4444
flags.xOrigin,
4545
flags.suppressLogs,
4646
previewPort,
47+
flags.noBrowser
4748
);
4849
}
4950
}

src/apps/cli/commands/start/studio.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default class StartStudio extends Command {
5555
}
5656
}
5757
this.metricsMetadata.port = port;
58-
await startStudio(filePath as string, port);
58+
startStudio(filePath as string, port,flags.noBrowser);
5959
}
6060

6161
private async parseArgs(args: Record<string, any>, port?: string) {

src/apps/cli/internal/flags/start/preview.flags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ export const previewFlags = () => {
2727
description: 'Pass this to suppress the detiled error logs.',
2828
default: false,
2929
}),
30+
noBrowser: Flags.boolean({char: 'B', description: 'Pass this to not open browser automatically upon running the command', default: false})
3031
};
3132
};

src/apps/cli/internal/flags/start/studio.flags.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ export const studioFlags = () => {
1313
description: 'port in which to start Studio',
1414
}),
1515
'no-interactive': Flags.boolean({
16-
description:
17-
'disable prompts for this command which asks for file path if not passed via the arguments.',
16+
description: 'disable prompts for this command which asks for file path if not passed via the arguments.',
1817
required: false,
1918
default: false,
2019
}),
20+
noBrowser: Flags.boolean({char: 'B', description: 'Pass this to not open browser automatically upon running the command', default: false})
2121
};
2222
};

src/domains/models/Preview.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ function isValidFilePath(filePath: string): boolean {
2525
}
2626

2727
// eslint-disable-next-line sonarjs/cognitive-complexity
28-
export async function startPreview(filePath:string,base:string | undefined,baseDirectory:string | undefined ,xOrigin:boolean | undefined,suppressLogs:boolean|undefined,port: number = DEFAULT_PORT):Promise<void> {
28+
export function startPreview(filePath:string,base:string | undefined,baseDirectory:string | undefined ,xOrigin:boolean | undefined,suppressLogs:boolean|undefined,port: number = DEFAULT_PORT, noBrowser?: boolean):void {
2929
if (filePath && !isValidFilePath(filePath)) {
3030
throw new SpecificationFileNotFound(filePath);
3131
}
32-
32+
3333
const baseDir = path.dirname(path.resolve(filePath));
3434
bundle(filePath).then((doc) => {
3535
if (doc) {
@@ -126,7 +126,23 @@ export async function startPreview(filePath:string,base:string | undefined,baseD
126126
}
127127
});
128128
}
129-
const server = createServer((req, res) => handle(req, res));
129+
130+
const server = createServer((req, res) => {
131+
if (req.url === '/close') {
132+
res.writeHead(200, { 'Content-Type': 'text/plain' });
133+
res.end('Shutting down server');
134+
for (const socket of wsServer.clients) {
135+
socket.close();
136+
}
137+
// Close the server
138+
server.close(() => {
139+
// eslint-disable-next-line no-process-exit
140+
process.exit(0);
141+
});
142+
return;
143+
}
144+
handle(req, res);
145+
});
130146

131147
server.on('upgrade', (request, socket, head) => {
132148
if (request.url === '/preview-server' && request.headers['origin'] === `http://localhost:${port}`) {
@@ -158,7 +174,7 @@ export async function startPreview(filePath:string,base:string | undefined,baseD
158174
'Warning: No file was provided, and we couldn\'t find a default file (like "asyncapi.yaml" or "asyncapi.json") in the current folder. Starting Studio with a blank workspace.'
159175
);
160176
}
161-
if (!bundleError) {
177+
if (!bundleError && !noBrowser) {
162178
open(url);
163179
}
164180
}).on('error', (error) => {

src/domains/models/Studio.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function isValidFilePath(filePath: string): boolean {
2121
}
2222

2323
// eslint-disable-next-line sonarjs/cognitive-complexity
24-
export async function start(filePath: string, port: number = DEFAULT_PORT): Promise<void> {
24+
export function start(filePath: string, port: number = DEFAULT_PORT, noBrowser?:boolean): void {
2525
if (filePath && !isValidFilePath(filePath)) {
2626
throw new SpecificationFileNotFound(filePath);
2727
}
@@ -116,7 +116,22 @@ export async function start(filePath: string, port: number = DEFAULT_PORT): Prom
116116
});
117117
}
118118

119-
const server = createServer((req, res) => handle(req, res));
119+
const server = createServer((req, res) => {
120+
if (req.url === '/close') {
121+
for (const socket of wsServer.clients) {
122+
socket.close();
123+
}
124+
res.writeHead(200, { 'Content-Type': 'application/json' });
125+
res.end(JSON.stringify({ message: 'Server is shutting down' }));
126+
// Close the server
127+
server.close(() => {
128+
// eslint-disable-next-line no-process-exit
129+
process.exit(0);
130+
});
131+
return;
132+
}
133+
handle(req, res);
134+
});
120135

121136
server.on('upgrade', (request, socket, head) => {
122137
if (request.url === '/live-server') {
@@ -145,7 +160,9 @@ export async function start(filePath: string, port: number = DEFAULT_PORT): Prom
145160
'Warning: No file was provided, and we couldn\'t find a default file (like "asyncapi.yaml" or "asyncapi.json") in the current folder. Starting Studio with a blank workspace.',
146161
);
147162
}
148-
open(url);
163+
if (!noBrowser) {
164+
open(url);
165+
}
149166
}).on('error', (error) => {
150167
if (error.message.includes('EADDRINUSE')) {
151168
console.log(error);

test/helpers/index.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { IContextFile, CONTEXT_FILE_PATH } from '../../src/domains/models/Contex
44
import SpecificationFile from '../../src/domains/models/SpecificationFile';
55
import http from 'http';
66
import rimraf from 'rimraf';
7+
import puppeteer from 'puppeteer';
8+
import { version as studioVersion } from '@asyncapi/studio/package.json';
79

810
const ASYNCAPI_FILE_PATH = path.resolve(process.cwd(), 'specification.yaml');
911
const SERVER_DIRECTORY= path.join(__dirname, '../fixtures/dummyspec');
@@ -77,6 +79,46 @@ export function fileCleanup(filepath: string) {
7779
unlinkSync(filepath);
7880
}
7981

82+
export async function testStudio(){
83+
const browser = await puppeteer.launch({
84+
args: ['--no-sandbox']
85+
});
86+
const page = await browser.newPage();
87+
88+
await page.goto(`http://127.0.0.1:3210?liveServer=3210&studio-version=${studioVersion}`);
89+
await page.setViewport({width: 1080, height: 1024});
90+
91+
const logo = await page.locator('body > div:nth-child(1) > div > div > div > div > img').waitHandle()
92+
const sideBar = await page.locator('#sidebar').waitHandle()
93+
const navigationPannel = await page.locator('#navigation-panel').waitHandle()
94+
const editor = await page.locator('#editor').waitHandle()
95+
96+
const logoTitle = await logo?.evaluate((e:any) => e.title)
97+
const sideBarId = await sideBar?.evaluate((e:any)=> e.id)
98+
const navigationPannelId = await navigationPannel?.evaluate((e:any)=> e.id)
99+
const editorId = await editor?.evaluate((e:any)=> e.id)
100+
await browser.close();
101+
return {logoTitle,sideBarId,navigationPannelId,editorId}
102+
}
103+
104+
export async function testPreview(){
105+
const browser = await puppeteer.launch({
106+
args: ['--no-sandbox']
107+
});
108+
const page = await browser.newPage();
109+
110+
await page.goto(`http://127.0.0.1:4321?previewServer=4321&studio-version=${studioVersion}`);
111+
await page.setViewport({width: 1080, height: 1024});
112+
113+
const logo = await page.locator('body > div:nth-child(1) > div > div > div > div > img').waitHandle()
114+
const introductionSection = await page.locator('#introduction').waitHandle()
115+
116+
const logoTitle = await logo?.evaluate((e:any) => e.title)
117+
const introductionSectionId = await introductionSection?.evaluate((e:any)=> e.id)
118+
119+
await browser.close();
120+
return {logoTitle,introductionSectionId}
121+
}
80122
export function createMockServer (port = 8080) {
81123
server = http.createServer(async (req,res) => {
82124
if (req.method ==='GET') {
@@ -103,6 +145,19 @@ export function stopMockServer() {
103145
server.close();
104146
}
105147

148+
export async function closeStudioServer(port = 3210): Promise<void> {
149+
try {
150+
const response = await fetch(`http://localhost:${port}/close`);
151+
if (response.ok) {
152+
const text = await response.text();
153+
} else {
154+
console.log(`Failed to close server. Status: ${response.status}`);
155+
}
156+
} catch (error) {
157+
console.error('Error closing studio server:', error);
158+
}
159+
}
160+
106161
function getContentType(filePath:string):string {
107162
const extname = path.extname(filePath);
108163
switch (extname) {

test/integration/studio.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { test } from '@oclif/test';
2+
import { expect } from '@oclif/test';
3+
import { testPreview, testStudio } from '../helpers';
4+
5+
describe('Test live studio', () => {
6+
test
7+
.stdout()
8+
.command([
9+
'start studio','-B','-p','3210','./test/fixtures/specification-v3.yml',
10+
])
11+
.it('should successfully open and navigate the site', async () => {
12+
const {logoTitle,sideBarId,navigationPannelId,editorId} = await testStudio();
13+
expect(logoTitle).to.equal('AsyncAPI Logo');
14+
expect(sideBarId).to.equal('sidebar');
15+
expect(navigationPannelId).to.equal('navigation-panel');
16+
expect(editorId).to.equal('editor');
17+
});
18+
});
19+
20+
describe('Test preview mode', () => {
21+
test
22+
.stdout()
23+
.command([
24+
'start preview','-B','-p','4321','./test/fixtures/asyncapi_v2.yml',
25+
])
26+
.it('should successfully open and navigate the site', async () => {
27+
const {logoTitle,introductionSectionId} = await testPreview();
28+
expect(logoTitle).to.equal('AsyncAPI Logo');
29+
expect(introductionSectionId).to.equal('introduction');
30+
});
31+
});

0 commit comments

Comments
 (0)