Skip to content

Commit

Permalink
added test server
Browse files Browse the repository at this point in the history
  • Loading branch information
imolorhe committed Jun 18, 2024
1 parent 4a2633a commit 7e47488
Show file tree
Hide file tree
Showing 16 changed files with 2,683 additions and 0 deletions.
90 changes: 90 additions & 0 deletions test-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
build
dumps
.vscode

.DS_Store

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/
6 changes: 6 additions & 0 deletions test-server/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"watch": ["src"],
"ext": "ts",
"ignore": ["*.test.ts"],
"delay": "3"
}
46 changes: 46 additions & 0 deletions test-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "ezio-tester",
"version": "1.0.0",
"description": "GraphQL server for testing Altair",
"main": "index.js",
"author": "[email protected]",
"license": "MIT",
"scripts": {
"build": "tsc",
"dev": "nodemon src/index.ts",
"dev:rs": "nodemon src/rs-index.ts --ignore src/schema/",
"dev:yoga": "nodemon src/yoga.ts"
},
"dependencies": {
"@apollo/server": "^4.5.0",
"@graphql-tools/schema": "^9.0.17",
"@graphql-yoga/plugin-defer-stream": "^3.3.1",
"axios": "^0.18.1",
"body-parser": "^1.20.2",
"chokidar": "^3.5.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.18.2",
"graphql": "^16.6.0",
"graphql-sse": "^2.1.3",
"graphql-subscriptions": "^2.0.0",
"graphql-upload": "15.0.2",
"graphql-ws": "^5.16.0",
"graphql-yoga": "^5.3.1",
"lodash": "^4.17.20",
"ws": "^8.13.0"
},
"devDependencies": {
"@types/compression": "^1.7.0",
"@types/express": "^4.17.17",
"@types/graphql": "^14.2.0",
"@types/graphql-upload": "^16.0.7",
"@types/lodash": "^4.14.123",
"@types/node": "^18.15.0",
"@types/ws": "^8.5.4",
"nodemon": "^2.0.22",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
}
}
2 changes: 2 additions & 0 deletions test-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { yogaMain } from "./yoga";
yogaMain();
45 changes: 45 additions & 0 deletions test-server/src/schema-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Express } from 'express';
import { EventEmitter } from 'events';

export const graphqlEventStream = ({
streamPath = '/stream',
emitter,
app,
}: { app: Express, streamPath: string, emitter: EventEmitter }) => {
app.use((req, res) => {
res.setHeader('X-GraphQL-Event-Stream', streamPath);
if (req.next) {
req.next();
}
});

app.get(streamPath, (req, res) => {
if (req.headers.accept !== 'text/event-stream') {
res.statusCode = 405;
res.end();
return;
}

res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Connection', 'keep-alive');
res.setHeader('Access-Control-Allow-Origin', '*');
res.flushHeaders(); // flush the headers to establish SSE with client

console.log('connected to client.');
let counter = 0;
const updateClients = () => {
counter++;
console.log('sending update to client..');
res.write(`data: ${JSON.stringify({num: counter})}\n\n`); // res.write() instead of res.send()
};
emitter.on('schema:updated', updateClients);

// If client closes connection, stop sending events
res.on('close', () => {
console.log('client dropped me.');
emitter.off('schema:updated', updateClients);
res.end();
});
});
};
47 changes: 47 additions & 0 deletions test-server/src/schema/File.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fileRepository = [];

const fakePath = (filename = "") => `x/y/z/${filename}`;

const processUpload = async (file: File) => {
const fileMetadata = {
filename: file.name,
mimetype: file.type,
encoding: "??",
filepath: fakePath(file.name),
};

fileRepository.push(fileMetadata);
return fileMetadata;
};

export const typeDef = `#graphql
extend type Query {
files: [MyFile]
}
extend type Mutation {
singleUpload(file: File!): MyFile
multipleUploads(files: [File!]!): [MyFile]
}
"""
MyFile **files** \`file?\`
"""
type MyFile {
filename: String!
mimetype: String!
encoding: String!
filepath: String!
}
`;

export const resolvers = {
Mutation: {
async singleUpload(root: any, { file }: { file: File }) {
return processUpload(file);
},
async multipleUploads(root: any, { files }: { files: File[] }) {
return await Promise.all(files.map(processUpload));
},
},
};
58 changes: 58 additions & 0 deletions test-server/src/schema/GOTBook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import axios from "axios";

export const typeDef = `#graphql
extend type Query {
GOTBooks(
name: String = "my-book"
): [GOTBook]
}
"""
<img src onerror="alert('hacked!')">
A Game of Thrones Book
### Real books
- First item
- Second item
- Third item
- Fourth items
"""
type GOTBook {
id: Int!
url: String
name: String
authors: [String]
characters: [GOTCharacter]
released: String
}
`;

export const resolvers = {
Query: {
GOTBooks: (root: any, args: any) =>
axios
.get(`https://www.anapioficeandfire.com/api/books`, { params: args })
.then((res) => res.data),
},
GOTBook: {
id(root: any) {
return root.url.match(/\d+/g).pop();
},
characters(root: any) {
if (root.characters && root.characters.length) {
return Promise.all(
root.characters
// Limit to the first 5 characters. Don't overload the API!
.filter((_: any, i: number) => i < 5)
.map(axios.get)
).then((charactersRes) => {
return charactersRes.map((characterRes: any) => characterRes.data);
});
}
return null;
},
},
};
82 changes: 82 additions & 0 deletions test-server/src/schema/GOTCharacter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import axios from "axios";

export const typeDef = `#graphql
extend type Query {
"""
## Test Heading
**bold**
- list1
- list2
Fields description
"""
GOTCharacters(
name: String,
gender: String,
culture: String,
born: String,
died: String,
isAlive: Boolean
): [GOTCharacter] @deprecated(reason: "Use GOTCharacter instead")
}
"""
ID as MongoDB ObjectId
## Test Heading
**bold**
- list1
- list2
"""
type GOTCharacter {
id: Int!
url: String
name: String
gender: String
culture: String
born: String
died: String
titles: [String]
aliases: [String]
father: GOTCharacter
mother: GOTCharacter
spouse: GOTCharacter
allegiances: [String]
books: [String]
playedBy: [String]
}
`;

export const resolvers = {
Query: {
GOTCharacters: (root: any, args: any) =>
axios
.get(`https://www.anapioficeandfire.com/api/characters`, {
params: args,
})
.then((res) => res.data),
},
GOTCharacter: {
id(root: any) {
return root.url.match(/\d+/g).pop();
},
father(root: any) {
if (root.father) {
return axios.get(root.father).then((res) => res.data);
}
return null;
},
mother(root: any) {
if (root.mother) {
return axios.get(root.mother).then((res) => res.data);
}
return null;
},
spouse(root: any) {
if (root.spouse) {
return axios.get(root.spouse).then((res) => res.data);
}
return null;
},
},
};
Loading

0 comments on commit 7e47488

Please sign in to comment.