// Make typescript aware of nodeunit import * as pf from "../index"; const reporter = require("./reporter"); const nodeunit = require("nodeunit"); import * as fs from "fs"; const PlayFab = pf.PlayFab as PlayFabModule.IPlayFab; const PlayFabAdmin = pf.PlayFabAdmin as PlayFabAdminModule.IPlayFabAdmin; const PlayFabClient = pf.PlayFabClient as PlayFabClientModule.IPlayFabClient; const PlayFabServer = pf.PlayFabServer as PlayFabServerModule.IPlayFabServer; const PlayFabAuthentication = pf.PlayFabAuthentication as PlayFabAuthenticationModule.IPlayFabAuthentication; const PlayFabCloudScript = pf.PlayFabCloudScript as PlayFabCloudScriptModule.IPlayFabCloudScript; const PlayFabData = pf.PlayFabData as PlayFabDataModule.IPlayFabData; const PlayFabEconomy = pf.PlayFabEconomy as PlayFabEconomyModule.IPlayFabEconomy; const PlayFabEvents = pf.PlayFabEvents as PlayFabEventsModule.IPlayFabEvents; const PlayFabExperimentation = pf.PlayFabExperimentation as PlayFabExperimentationModule.IPlayFabExperimentation; const PlayFabInsights = pf.PlayFabInsights as PlayFabInsightsModule.IPlayFabInsights; const PlayFabGroups = pf.PlayFabGroups as PlayFabGroupsModule.IPlayFabGroups; const PlayFabProgression = pf.PlayFabProgression as PlayFabProgressionModule.IPlayFabProgression; const PlayFabLocalization = pf.PlayFabLocalization as PlayFabLocalizationModule.IPlayFabLocalization; const PlayFabMultiplayer = pf.PlayFabMultiplayer as PlayFabMultiplayerModule.IPlayFabMultiplayer; const PlayFabProfiles = pf.PlayFabProfiles as PlayFabProfilesModule.IPlayFabProfiles; const PlayFabAddon = pf.PlayFabAddon as PlayFabAddonModule.IPlayFabAddon; type IAction = () => void; let titleData = { // You can set default values for testing here // Or you can provide the same structure in a json-file and load with LoadTitleData titleId: "", developerSecretKey: "", userEmail: "", }; const testConstants = { TEST_KEY: "testCounter", TEST_STAT_NAME: "str", }; interface TestDataGlobal { entityId: string | null | undefined; entityType: string | null | undefined; playFabId: string | null | undefined; testNumber: number | null | undefined; } const testData: TestDataGlobal = { entityId: "", entityType: "", playFabId: "", testNumber: 0, }; function TestWrapper(testFunc: (test: any) => void): (test: any) => void { // The purpose of this TestWrapper is to report tests as failures when they throw exceptions. // It's pretty disappointing that this isn't part of the testing library return (test: any): void => { try { testFunc(test); } catch (e) { test.ok(false, "Exception thrown during test: " + e.toString() + "\n" + e.stack); test.done(); // This is required to display the error above, and abort the test } }; } function CallbackWrapper( callbackName: string, callback: PlayFabModule.ApiCallback, test: any, ) { // Wrap PlayFab result callbacks so that exceptions in callbacks report into the test as failures // This is is specific to catching exceptions in the PlayFab callbacks, since they're async, // they don't share the same stacktrace as the function that calls them return (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { try { callback(error, result); } catch (e) { test.ok(false, "Exception thrown during " + callbackName + " callback: " + e.toString() + "\n" + e.stack); test.done(); // This is required to display the error above, and abort the test } }; } function SimpleCallbackWrapper(callbackName: string, callback: IAction, test: any): IAction { // Wrap no-parameter callbacks so that exceptions in callbacks report into the test as failures // This is is specific to catching exceptions in the PlayFab callbacks, since they're async, // they don't share the same stacktrace as the function that calls them return (): void => { try { callback(); } catch (e) { test.ok(false, "Exception thrown during " + callbackName + " callback: " + e.toString() + "\n" + e.stack); test.done(); // This is required to display the error above, and abort the test } }; } function VerifyNullError(result: any, error: PlayFabModule.IPlayFabError, test: any, message: string): void { const success = result !== null && error == null; if (error != null) { test.ok(false, "PlayFab error message: " + CompileErrorReport(error)); } else { test.ok(success, message); } } function CompileErrorReport(error: PlayFabModule.IPlayFabError): string { if (error == null) return ""; let fullErrors = error.errorMessage; for (const paramName in error.errorDetails) { for (const msgIdx in error.errorDetails[paramName]) { fullErrors += "\n" + paramName + ": " + error.errorDetails[paramName][msgIdx]; } } return fullErrors; } exports.PlayFabApiTests = { setUp(callback: () => void): void { let filename = process.env.PF_TEST_TITLE_DATA_JSON; // Set the PF_TEST_TITLE_DATA_JSON env-var to the path of a testTitleData.json file (described here: https://github.com/PlayFab/SDKGenerator/blob/master/JenkinsConsoleUtility/testTitleData.md) if (!filename) throw new Error("testTitleData.json file location not defined."); const prefix = "testTitleData="; for (const arg in process.argv) { if (arg.toLowerCase().indexOf(prefix) === 0) { filename = arg.substr(prefix.length, arg.length - prefix.length); } } if (filename != null && fs.existsSync(filename)) { const inputTitleData = require(filename); // All of these must exist for the titleData load to be successful const titleDataValid = inputTitleData.hasOwnProperty("titleId") && inputTitleData.hasOwnProperty("developerSecretKey") && inputTitleData.hasOwnProperty("userEmail"); if (titleDataValid) { titleData = inputTitleData; } else { console.log("testTitleData input file did not parse correctly"); } } PlayFab.settings.titleId = titleData.titleId; PlayFab.settings.developerSecretKey = titleData.developerSecretKey; callback(); }, tearDown(callback: () => void): void { callback(); }, /// /// CLIENT API /// Try to deliberately log in with an inappropriate password, /// and verify that the error displays as expected. /// InvalidLogin: TestWrapper((test): void => { const invalidRequest = { Email: titleData.userEmail, Password: "INVALID", }; const invalidLoginCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { test.ok(result == null, "Login should have failed"); test.ok(error != null, "Login should have failed"); if (error != null) { test.ok(error.errorMessage.toLowerCase().indexOf("password") > -1, error.errorMessage); } test.done(); }; PlayFabClient.LoginWithEmailAddress(invalidRequest, CallbackWrapper("invalidLoginCallback", invalidLoginCallback, test)); }), /// /// CLIENT API /// Try to deliberately register a user with an invalid email and password /// Verify that errorDetails are populated correctly. /// InvalidRegistration: TestWrapper((test): void => { const invalidRequest = { Username: "x", Email: "x", Password: "x", }; const registerCallback = (error: PlayFabModule.IPlayFabError, result: any): void => { test.ok(result == null, "InvalidRegistration should have failed"); test.ok(error != null, "InvalidRegistration should have failed"); const expectedEmailMsg = "email address is not valid."; const expectedPasswordMsg = "password must be between"; const errorReport = CompileErrorReport(error); test.ok(errorReport.toLowerCase().indexOf(expectedEmailMsg) > -1, "Expect errorMessage about invalid email: " + errorReport); test.ok(errorReport.toLowerCase().indexOf(expectedPasswordMsg) > -1, "Expect errorMessage about invalid password: " + errorReport); test.done(); }; PlayFabClient.RegisterPlayFabUser(invalidRequest, CallbackWrapper("registerCallback", registerCallback, test)); }), /// /// CLIENT API /// Log in or create a user, track their PlayFabId /// LoginOrRegister: TestWrapper((test): void => { const loginRequest = { CustomId: PlayFab.buildIdentifier, CreateAccount: true, }; const loginCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing Valid login result"); test.ok(PlayFabClient.IsClientLoggedIn(), "Testing Login credentials cache"); testData.playFabId = result.data.PlayFabId; // Save the PlayFabId, it will be used in other tests test.done(); }; PlayFabClient.LoginWithCustomID(loginRequest, CallbackWrapper("loginCallback", loginCallback, test)); }), /// /// CLIENT API /// Test a sequence of calls that modifies saved data, /// and verifies that the next sequential API call contains updated data. /// Verify that the data is correctly modified on the next call. /// Parameter types tested: string, Dictionary, DateTime /// UserDataApi: TestWrapper((test): void => { const getDataRequest = {}; // null also works const getDataCallback2 = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetUserData result"); test.ok(result.data.Data != null, "GetUserData failed"); test.ok(result.data.Data!.hasOwnProperty(testConstants.TEST_KEY), "GetUserData failed"); const actualtestNumber: number = parseInt(result.data.Data![testConstants.TEST_KEY].Value!, 10); const actualTimeStamp: number = new Date(result.data.Data![testConstants.TEST_KEY].LastUpdated).getTime(); test.equal(testData.testNumber, actualtestNumber, "" + testData.testNumber + "!=" + actualtestNumber); const now: number = Date.now(); const testMin: number = now - 1000 * 60 * 5; const testMax: number = now + 1000 * 60 * 5; test.ok(testMin <= actualTimeStamp && actualTimeStamp <= testMax); test.done(); }; const updateDataCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing UpdateUserData result"); PlayFabClient.GetUserData(getDataRequest, CallbackWrapper("getDataCallback2", getDataCallback2, test)); }; const getDataCallback1 = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetUserData result"); test.ok(result.data.Data != null, "GetUserData failed"); const hasData = result.data.Data!.hasOwnProperty(testConstants.TEST_KEY); testData.testNumber = !hasData ? 1 : parseInt(result.data.Data![testConstants.TEST_KEY].Value!, 10); testData.testNumber = (testData.testNumber + 1) % 100; // This test is about the expected value changing - but not testing more complicated issues like bounds const updateDataRequest: PlayFabClientModels.UpdateUserDataRequest = {}; updateDataRequest.Data = {}; updateDataRequest.Data[testConstants.TEST_KEY] = testData.testNumber.toString(); PlayFabClient.UpdateUserData(updateDataRequest, CallbackWrapper("updateDataCallback", updateDataCallback, test)); }; // Kick off this test process PlayFabClient.GetUserData(getDataRequest, CallbackWrapper("updateDataCallback", getDataCallback1, test)); }), /// /// CLIENT API /// Test a sequence of calls that modifies saved data, /// and verifies that the next sequential API call contains updated data. /// Verify that the data is saved correctly, and that specific types are tested /// Parameter types tested: Dictionary /// PlayerStatisticsApi: TestWrapper((test): void => { const getStatsRequest = {}; // null also works const getStatsCallback2 = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetPlayerStats result"); test.ok(result.data.Statistics != null, "GetPlayerStats failed"); let actualtestNumber = -1000; for (const eachStat of result.data.Statistics!) { if (eachStat.StatisticName === testConstants.TEST_STAT_NAME) { actualtestNumber = eachStat.Value; } } test.equal(testData.testNumber, actualtestNumber, "" + testData.testNumber + "!=" + actualtestNumber); test.done(); }; const updateStatsCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing UpdatePlayerStats result"); PlayFabClient.GetPlayerStatistics(getStatsRequest, CallbackWrapper("getStatsCallback2", getStatsCallback2, test)); }; const getStatsCallback1 = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetPlayerStats result"); test.ok(result.data.Statistics != null, "GetPlayerStats failed"); testData.testNumber = 0; for (const eachStat of result.data.Statistics!) { if (eachStat.StatisticName === testConstants.TEST_STAT_NAME) { testData.testNumber = eachStat.Value; } } testData.testNumber = (testData.testNumber + 1) % 100; // This test is about the expected value changing - but not testing more complicated issues like bounds const updateStatsRequest = { Statistics: [ { StatisticName: testConstants.TEST_STAT_NAME, Value: testData.testNumber, }, ], }; PlayFabClient.UpdatePlayerStatistics(updateStatsRequest, CallbackWrapper("updateStatsCallback", updateStatsCallback, test)); }; // Kick off this test process PlayFabClient.GetPlayerStatistics(getStatsRequest, CallbackWrapper("getStatsCallback1", getStatsCallback1, test)); }), /// /// SERVER API /// Get or create the given test character for the given user /// Parameter types tested: Contained-Classes, string /// UserCharacter: TestWrapper((test): void => { const getCharsCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetChars result"); test.done(); }; const getCharsRequest = {}; PlayFabClient.GetAllUsersCharacters(getCharsRequest, CallbackWrapper("getCharsCallback", getCharsCallback, test)); }), /// /// CLIENT AND SERVER API /// Test that leaderboard results can be requested /// Parameter types tested: List of contained-classes /// LeaderBoard: TestWrapper((test): void => { const clientRequest: PlayFabClientModels.GetLeaderboardRequest = { MaxResultsCount: 3, StartPosition: 0, StatisticName: testConstants.TEST_STAT_NAME, }; const serverRequest: PlayFabServerModels.GetLeaderboardRequest = { MaxResultsCount: 3, StartPosition: 0, StatisticName: testConstants.TEST_STAT_NAME, }; let callsCompleted = 0; const getLeaderboardCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetLeaderboard result"); if (result != null) { test.ok(result.data.Leaderboard != null, "GetLeaderboard failed"); test.ok(result.data.Leaderboard!.length > 0, "Leaderboard had insufficient entries"); } callsCompleted += 1; if (callsCompleted === 2) test.done(); }; PlayFabClient.GetLeaderboard(clientRequest, CallbackWrapper("getLeaderboardCallback", getLeaderboardCallback, test)); PlayFabServer.GetLeaderboard(serverRequest, CallbackWrapper("getLeaderboardCallback", getLeaderboardCallback, test)); }), /// /// CLIENT API /// Test that AccountInfo can be requested /// Parameter types tested: List of enum-as-strings converted to list of enums /// AccountInfo: TestWrapper((test): void => { const getAccountInfoCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetAccountInfo result"); test.ok(result.data.AccountInfo != null, "GetAccountInfo failed"); test.ok(result.data.AccountInfo!.TitleInfo != null, "GetAccountInfo failed"); test.ok(result.data.AccountInfo!.TitleInfo!.Origination != null, "GetAccountInfo failed"); test.ok(result.data.AccountInfo!.TitleInfo!.Origination!.length > 0, "GetAccountInfo string-Enum failed"); test.done(); }; PlayFabClient.GetAccountInfo({}, CallbackWrapper("getAccountInfoCallback", getAccountInfoCallback, test)); }), /// /// CLIENT API /// Test that CloudScript can be properly set up and invoked /// CloudScript: TestWrapper((test): void => { const helloWorldRequest = { FunctionName: "helloWorld", }; const helloWorldCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing HelloWorld result"); if (result != null) { test.ok(result.data.FunctionResult != null, "HelloWorld failed"); test.ok(result.data.FunctionResult.messageValue != null, "HelloWorld failed"); test.equal(result.data.FunctionResult.messageValue, "Hello " + testData.playFabId + "!", "Unexpected HelloWorld cloudscript result: " + result.data.FunctionResult.messageValue); } test.done(); }; PlayFabClient.ExecuteCloudScript(helloWorldRequest, CallbackWrapper("helloWorldCallback", helloWorldCallback, test)); }), /// /// CLIENT API /// Test that CloudScript errors can be deciphered /// CloudScriptError: TestWrapper((test): void => { const errRequest = { FunctionName: "throwError", }; const errCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing Cloud Script Error result"); if (result != null) { test.ok(result.data.FunctionResult == null, "Cloud Script Error failed"); test.ok(result.data.Error != null, "Cloud Script Error failed"); test.equal(result.data.Error!.Error, "JavascriptException", "Cloud Script Error failed"); } test.done(); }; PlayFabClient.ExecuteCloudScript(errRequest, CallbackWrapper("errCallback", errCallback, test)); }), /// /// CLIENT API /// Test that the client can publish custom PlayStream events /// WriteEvent: TestWrapper((test): void => { const writeEventRequest = { EventName: "ForumPostEvent", Body: { Subject: "My First Post", Body: "This is my awesome post.", }, }; const writeEventCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing WriteEvent result"); test.done(); }; PlayFabClient.WritePlayerEvent(writeEventRequest, CallbackWrapper("writeEventCallback", writeEventCallback, test)); }), /// /// ENTITY API /// Verify that a client login can be converted into an entity token /// GetEntityToken: TestWrapper((test): void => { const getEntityTokenCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetEntityToken result"); test.ok(result.data.Entity!.Id, "EntityId should be defined"); test.ok(result.data.Entity!.Type, "entityType should be defined"); testData.entityId = result.data.Entity!.Id; // Save the Entity info, it will be used in other tests testData.entityType = result.data.Entity!.Type; // Save the Entity info, it will be used in other tests test.done(); }; PlayFabAuthentication.GetEntityToken(null, CallbackWrapper("getEntityTokenCallback", getEntityTokenCallback, test)); }), /// /// ENTITY API /// Test a sequence of calls that modifies entity objects, /// and verifies that the next sequential API call contains updated information. /// Verify that the object is correctly modified on the next call. /// ObjectApi: TestWrapper((test): void => { const getObjectsRequest: PlayFabDataModels.GetObjectsRequest = { Entity: { Id: testData.entityId!, Type: testData.entityType!, }, EscapeObject: true, }; const getObjCallback2 = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetObjects result"); test.ok(result.data.Objects, "GetObjects failed"); test.ok(result.data.Objects!.hasOwnProperty(testConstants.TEST_KEY)); const actualtestNumber: number = parseInt(result.data.Objects![testConstants.TEST_KEY].EscapedDataObject!, 10); test.equal(testData.testNumber, actualtestNumber, "" + testData.testNumber + " !== " + actualtestNumber); test.done(); }; const setObjCallback = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing SetObjects result"); PlayFabData.GetObjects(getObjectsRequest, CallbackWrapper("getObjCallback2", getObjCallback2, test)); }; const getObjCallback1 = (error: PlayFabModule.IPlayFabError, result: PlayFabModule.IPlayFabSuccessContainer): void => { VerifyNullError(result, error, test, "Testing GetObjects result"); testData.testNumber = 0; if (result.data.Objects && result.data.Objects.hasOwnProperty(testConstants.TEST_KEY)) { testData.testNumber = parseInt(result.data.Objects[testConstants.TEST_KEY].EscapedDataObject!, 10); } testData.testNumber = (testData.testNumber + 1) % 100; // This test is about the expected value changing - but not testing more complicated issues like bounds const setObjRequest: PlayFabDataModels.SetObjectsRequest = { Entity: { Id: testData.entityId!, Type: testData.entityType!, }, Objects: [ { ObjectName: testConstants.TEST_KEY, DataObject: testData.testNumber, }, ], }; PlayFabData.SetObjects(setObjRequest, CallbackWrapper("setObjCallback", setObjCallback, test)); }; // Kick off this test process PlayFabData.GetObjects(getObjectsRequest, CallbackWrapper("getObjCallback1", getObjCallback1, test)); }), }; nodeunit.on("complete", (): void => { reporter.PfTestReport[0].name = PlayFab.buildIdentifier; const saveResultsRequest = { FunctionName: "SaveTestData", FunctionParameter: { customId: PlayFab.buildIdentifier, testReport: reporter.PfTestReport, }, GeneratePlayStreamEvent: true, }; if (PlayFabClient.IsClientLoggedIn()) { PlayFabClient.ExecuteCloudScript(saveResultsRequest, null); console.log(testData.playFabId, ", Test report saved to CloudScript: ", PlayFab.buildIdentifier); // , "\n", JSON.stringify(reporter.PfTestReport, null, 4)); } else { console.log(testData.playFabId, ", Failed to save test report to CloudScript: ", PlayFab.buildIdentifier); // , "\n", JSON.stringify(reporter.PfTestReport, null, 4)); } });