mirror of
https://github.com/PretendoNetwork/miiverse-api.git
synced 2024-05-18 12:50:41 -04:00
Added the start of unit tests
This commit is contained in:
parent
dc040731bb
commit
11fb673fc8
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -132,4 +132,5 @@ dist
|
|||
# custom
|
||||
certs
|
||||
logs
|
||||
dist
|
||||
dist
|
||||
newman
|
2846
package-lock.json
generated
2846
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -8,7 +8,7 @@
|
|||
"build": "npm run lint && npm run clean && npx tsc && npx tsc-alias",
|
||||
"clean": "rm -rf ./dist",
|
||||
"start": "node .",
|
||||
"start:dev": "NODE_ENV=development node ."
|
||||
"test": "ts-node test/test.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Jemma",
|
||||
|
@ -41,14 +41,20 @@
|
|||
"@types/memoizee": "^0.4.8",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/newman": "^5.3.2",
|
||||
"@types/node-rsa": "^1.1.1",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"axios": "^1.3.6",
|
||||
"eslint": "^8.38.0",
|
||||
"newman": "^5.3.2",
|
||||
"newman-reporter-json-summary": "^1.0.14",
|
||||
"postman-collection": "^4.1.7",
|
||||
"ts-unused-exports": "^9.0.4",
|
||||
"tsc-alias": "^1.8.5",
|
||||
"typescript": "^5.0.4"
|
||||
"typescript": "^5.0.4",
|
||||
"xmlbuilder2": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
|
226
test/test.ts
Normal file
226
test/test.ts
Normal file
|
@ -0,0 +1,226 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import crypto from 'node:crypto';
|
||||
import newman from 'newman';
|
||||
import { Collection, CollectionDefinition } from 'postman-collection';
|
||||
import qs from 'qs';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { create as parseXML } from 'xmlbuilder2';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
import communitiesCollection from '../postman/collections/Communities.json';
|
||||
import peopleCollection from '../postman/collections/People.json';
|
||||
|
||||
const PeopleCollection: CollectionDefinition = communitiesCollection as CollectionDefinition;
|
||||
const CommunitiesCollection: CollectionDefinition = peopleCollection as CollectionDefinition;
|
||||
|
||||
dotenv.config();
|
||||
|
||||
interface CondensedSummary {
|
||||
collection: {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
run: {
|
||||
stats: {
|
||||
requests: newman.NewmanRunStat;
|
||||
assertions: newman.NewmanRunStat;
|
||||
};
|
||||
failures: {
|
||||
parent: {
|
||||
name: string;
|
||||
id: string;
|
||||
},
|
||||
source: {
|
||||
name: string;
|
||||
id: string;
|
||||
},
|
||||
error: {
|
||||
message: string;
|
||||
test: string;
|
||||
}
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
const USERNAME: string = process.env.PN_MIIVERSE_API_TESTING_USERNAME?.trim() || '';
|
||||
const PASSWORD: string = process.env.PN_MIIVERSE_API_TESTING_PASSWORD?.trim() || '';
|
||||
|
||||
if (!USERNAME) {
|
||||
throw new Error('PNID username missing. Required for requesting service tokens. Set PN_MIIVERSE_API_TESTING_USERNAME');
|
||||
}
|
||||
|
||||
if (!PASSWORD) {
|
||||
throw new Error('PNID password missing. Required for requesting service tokens. Set PN_MIIVERSE_API_TESTING_PASSWORD');
|
||||
}
|
||||
|
||||
const BASE_URL: string = 'https://account.pretendo.cc';
|
||||
const API_URL: string = `${BASE_URL}/v1/api`;
|
||||
const MAPPED_IDS_URL: string = `${API_URL}/admin/mapped_ids`;
|
||||
const ACCESS_TOKEN_URL: string = `${API_URL}/oauth20/access_token/generate`;
|
||||
const SERVICE_TOKEN_URL: string = `${API_URL}/provider/service_token/@me`;
|
||||
|
||||
const DEFAULT_HEADERS: Record<string, string> = {
|
||||
'X-Nintendo-Client-ID': 'a2efa818a34fa16b8afbc8a74eba3eda',
|
||||
'X-Nintendo-Client-Secret': 'c91cdb5658bd4954ade78533a339cf9a'
|
||||
};
|
||||
|
||||
export function nintendoPasswordHash(password: string, pid: number): string {
|
||||
const pidBuffer: Buffer = Buffer.alloc(4);
|
||||
pidBuffer.writeUInt32LE(pid);
|
||||
|
||||
const unpacked: Buffer = Buffer.concat([
|
||||
pidBuffer,
|
||||
Buffer.from('\x02\x65\x43\x46'),
|
||||
Buffer.from(password)
|
||||
]);
|
||||
|
||||
return crypto.createHash('sha256').update(unpacked).digest().toString('hex');
|
||||
}
|
||||
|
||||
async function apiGetRequest(url: string, headers = {}): Promise<Record<string, any>> {
|
||||
const response: AxiosResponse<any, any> = await axios.get(url, {
|
||||
headers: Object.assign(headers, DEFAULT_HEADERS),
|
||||
validateStatus: () => true
|
||||
});
|
||||
|
||||
const data: Record<string, any> = parseXML(response.data).end({ format: 'object' });
|
||||
|
||||
if (data.errors) {
|
||||
throw new Error(data.errors.error.message);
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error.message);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function apiPostRequest(url: string, body: string): Promise<Record<string, any>> {
|
||||
const response: AxiosResponse<any, any> = await axios.post(url, body, {
|
||||
headers: DEFAULT_HEADERS,
|
||||
validateStatus: () => true,
|
||||
});
|
||||
|
||||
const data: Record<string, any> = parseXML(response.data).end({ format: 'object' });
|
||||
|
||||
if (data.errors) {
|
||||
throw new Error(data.errors.error.message);
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error.message);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getPID(username: string): Promise<number> {
|
||||
const response: Record<string, any> = await apiGetRequest(`${MAPPED_IDS_URL}?input_type=user_id&output_type=pid&input=${username}`);
|
||||
|
||||
return Number(response.mapped_ids.mapped_id.out_id);
|
||||
}
|
||||
|
||||
async function getAccessToken(username: string, passwordHash: string): Promise<string> {
|
||||
const data: string = qs.stringify({
|
||||
grant_type: 'password',
|
||||
user_id: username,
|
||||
password: passwordHash,
|
||||
password_type: 'hash',
|
||||
});
|
||||
|
||||
const response: Record<string, any> = await apiPostRequest(ACCESS_TOKEN_URL, data);
|
||||
|
||||
return response.OAuth20.access_token.token;
|
||||
}
|
||||
|
||||
async function getMiiverseServiceToken(accessToken: string): Promise<string> {
|
||||
const response: Record<string, any> = await apiGetRequest(SERVICE_TOKEN_URL, {
|
||||
'X-Nintendo-Title-ID': '0005001010040100',
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
});
|
||||
|
||||
return response.service_token.token;
|
||||
}
|
||||
|
||||
function runNewmanTest(collection: string | Collection | CollectionDefinition, variables: Record<string, string>): Promise<CondensedSummary> {
|
||||
return new Promise((resolve, reject) => {
|
||||
newman.run({
|
||||
collection: collection,
|
||||
reporters: ['cli', 'json'],
|
||||
envVar: Object.entries(variables).map(entry => ({ key: entry[0], value: entry[1] }))
|
||||
}, (error, summary) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(createCondensedSummary(summary));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function communitiesRoutesTest(serviceToken: string): Promise<CondensedSummary> {
|
||||
// TODO - Make this more dynamic?
|
||||
return runNewmanTest(CommunitiesCollection, {
|
||||
DOMAIN: 'api.olv.pretendo.cc',
|
||||
ServiceToken: serviceToken,
|
||||
// TODO - Change this name. Should not be game-specific
|
||||
PP_Splatoon: 'XHRpdGxlX2lkXDE0MDczNzUxNTM1MjI5NDRcYWNjZXNzX2tleVwwXHBsYXRmb3JtX2lkXDFccmVnaW9uX2lkXDJcbGFuZ3VhZ2VfaWRcMVxjb3VudHJ5X2lkXDExMFxhcmVhX2lkXDBcbmV0d29ya19yZXN0cmljdGlvblwwXGZyaWVuZF9yZXN0cmljdGlvblwwXHJhdGluZ19yZXN0cmljdGlvblwyMFxyYXRpbmdfb3JnYW5pemF0aW9uXDBcdHJhbnNmZXJhYmxlX2lkXDEyNzU2MTQ0ODg0NDUzODk4NzgyXHR6X25hbWVcQW1lcmljYS9OZXdfWW9ya1x1dGNfb2Zmc2V0XC0xNDQwMFxyZW1hc3Rlcl92ZXJzaW9uXDBc',
|
||||
// TODO - Change this name. Should not be game-specific
|
||||
PP_MarioVsDK: 'XHRpdGxlX2lkXDE0MDczNzUxNTMzMzcwODhcYWNjZXNzX2tleVw2OTI0NzQ1MTBccGxhdGZvcm1faWRcMVxyZWdpb25faWRcMlxsYW5ndWFnZV9pZFwxXGNvdW50cnlfaWRcNDlcYXJlYV9pZFwwXG5ldHdvcmtfcmVzdHJpY3Rpb25cMFxmcmllbmRfcmVzdHJpY3Rpb25cMFxyYXRpbmdfcmVzdHJpY3Rpb25cMTdccmF0aW5nX29yZ2FuaXphdGlvblwxXHRyYW5zZmVyYWJsZV9pZFw3NjA4MjAyOTE2MDc1ODg0NDI1XHR6X25hbWVcUGFjaWZpYy9NaWR3YXlcdXRjX29mZnNldFwtMzk2MDBc',
|
||||
PP_Bad_TID: '000',
|
||||
'PP_Bad Format': 'XHR'
|
||||
});
|
||||
}
|
||||
|
||||
function peopleRoutesTest(serviceToken: string): Promise<CondensedSummary> {
|
||||
// TODO - Make this more dynamic?
|
||||
return runNewmanTest(PeopleCollection, {
|
||||
DOMAIN: 'api.olv.pretendo.cc',
|
||||
ServiceToken: serviceToken,
|
||||
// TODO - Change this name. Should not be game-specific
|
||||
PP_Splatoon: 'XHRpdGxlX2lkXDE0MDczNzUxNTM1MjI5NDRcYWNjZXNzX2tleVwwXHBsYXRmb3JtX2lkXDFccmVnaW9uX2lkXDJcbGFuZ3VhZ2VfaWRcMVxjb3VudHJ5X2lkXDExMFxhcmVhX2lkXDBcbmV0d29ya19yZXN0cmljdGlvblwwXGZyaWVuZF9yZXN0cmljdGlvblwwXHJhdGluZ19yZXN0cmljdGlvblwyMFxyYXRpbmdfb3JnYW5pemF0aW9uXDBcdHJhbnNmZXJhYmxlX2lkXDEyNzU2MTQ0ODg0NDUzODk4NzgyXHR6X25hbWVcQW1lcmljYS9OZXdfWW9ya1x1dGNfb2Zmc2V0XC0xNDQwMFxyZW1hc3Rlcl92ZXJzaW9uXDBc',
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const pid: number = await getPID(USERNAME);
|
||||
const passwordHash: string = nintendoPasswordHash(PASSWORD, pid);
|
||||
const accessToken: string = await getAccessToken(USERNAME, passwordHash);
|
||||
const serviceToken: string = await getMiiverseServiceToken(accessToken);
|
||||
|
||||
await communitiesRoutesTest(serviceToken);
|
||||
await peopleRoutesTest(serviceToken);
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
function createCondensedSummary(summary: newman.NewmanRunSummary): CondensedSummary {
|
||||
return {
|
||||
collection: {
|
||||
name: summary.collection.name,
|
||||
id: summary.collection.id
|
||||
},
|
||||
run: {
|
||||
stats: {
|
||||
requests : summary.run.stats.requests,
|
||||
assertions : summary.run.stats.assertions
|
||||
},
|
||||
failures: summary.run.failures.map((failure: newman.NewmanRunFailure) => ({
|
||||
parent: {
|
||||
name: failure.parent.name,
|
||||
id : failure.parent.id
|
||||
},
|
||||
source: {
|
||||
name: failure.source?.name || 'Unknown',
|
||||
id : failure.source?.id || 'Unknown'
|
||||
},
|
||||
error: {
|
||||
message: failure.error.message,
|
||||
test : failure.error.test
|
||||
}
|
||||
}))
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue