diff --git a/TShock.sln b/TShock.sln index 85d6a0f9..db301cae 100644 --- a/TShock.sln +++ b/TShock.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Terraria.vsmdi = Terraria.vsmdi EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TShockRestTestPlugin", "TShockRestTestPlugin\TShockRestTestPlugin.csproj", "{F2FEDAFB-58DE-4611-9168-A86112C346C7}" +EndProject Global GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = Terraria.vsmdi @@ -52,6 +54,16 @@ Global {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|x86.ActiveCfg = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Any CPU.Build.0 = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 4fd20273..ed79492e 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using HttpServer; @@ -36,395 +37,393 @@ namespace TShockAPI public void RegisterRestfulCommands() { - Rest.Register(new RestCommand("/status", Status) {RequiresToken = false}); - Rest.Register(new RestCommand("/tokentest", TokenTest) {RequiresToken = true}); + // Server Commands + Rest.Register(new RestCommand("/v2/server/broadcast", ServerBroadcast)); + Rest.Register(new RestCommand("/v2/server/off", ServerOff)); + Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand)); + Rest.Register(new RestCommand("/v2/server/status", ServerStatusV2) { RequiresToken = false }); + Rest.Register(new RestCommand("/tokentest", ServerTokenTest)); + Rest.Register(new RestCommand("/status", ServerStatus) { RequiresToken = false }); - Rest.Register(new RestCommand("/v2/users/activelist", UserListV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/read", UserInfoV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2) { RequiresToken = true }); + // User Commands + Rest.Register(new RestCommand("/v2/users/activelist", UserActiveListV2)); + Rest.Register(new RestCommand("/v2/users/create", UserCreateV2)); + Rest.Register(new RestCommand("/v2/users/list", UserListV2)); + Rest.Register(new RestCommand("/v2/users/read", UserInfoV2)); + Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2)); + Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2)); - Rest.Register(new RestCommand("/bans/create", BanCreate) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2) { RequiresToken = true }); + // Ban Commands + Rest.Register(new RestCommand("/bans/create", BanCreate)); + Rest.Register(new RestCommand("/v2/bans/list", BanListV2)); + Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2)); + Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2)); - Rest.Register(new RestCommand("/v2/lists/bans", BanListIPs) { RequiresToken = true }); - Rest.Register(new RestCommand("/lists/players", PlayerList) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/lists/players", PlayerListV2) { RequiresToken = true }); + // World Commands + Rest.Register(new RestCommand("/world/read", WorldRead)); + Rest.Register(new RestCommand("/world/meteor", WorldMeteor)); + Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon)); + Rest.Register(new RestCommand("/v2/world/save", WorldSave)); + Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings)); + Rest.Register(new RestCommand("/v2/world/butcher", WorldButcher)); - Rest.Register(new RestCommand("/world/read", WorldRead) {RequiresToken = true}); - Rest.Register(new RestCommand("/world/meteor", WorldMeteor) {RequiresToken = true}); - Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/world/save", WorldSave) { RequiresToken = true}); - Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", ChangeWorldSaveSettings) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/world/butcher", Butcher) {RequiresToken = true}); + // Player Commands + Rest.Register(new RestCommand("/lists/players", PlayerList)); + Rest.Register(new RestCommand("/v2/players/list", PlayerListV2)); + Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2)); + Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2)); + Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2)); + Rest.Register(new RestCommand("/v2/players/kill", PlayerKill)); + Rest.Register(new RestCommand("/v2/players/mute", PlayerMute)); + Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute)); - Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/kill", PlayerKill) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/players/mute", PlayerMute) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute) {RequiresToken = true}); - - Rest.Register(new RestCommand("/v2/server/broadcast", Broadcast) { RequiresToken = true}); - Rest.Register(new RestCommand("/v2/server/off", Off) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand) {RequiresToken = true}); + // Group Commands + Rest.Register(new RestCommand("/v2/groups/list", GroupList)); + Rest.Register(new RestCommand("/v2/groups/read", GroupInfo)); + Rest.Register(new RestCommand("/v2/groups/destroy", GroupDestroy)); + Rest.Register(new RestCommand("/v2/groups/create", GroupCreate)); + Rest.Register(new RestCommand("/v2/groups/update", GroupUpdate)); } #region RestServerMethods private object ServerCommand(RestVerbs verbs, IParameterCollection parameters) { - if (parameters["cmd"] != null && parameters["cmd"].Trim() != "") - { - TSRestPlayer tr = new TSRestPlayer(); - RestObject ro = new RestObject("200"); - Commands.HandleCommand(tr, parameters["cmd"]); - foreach (string s in tr.GetCommandOutput()) - { - ro.Add("response", s); - } - return ro; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Missing or blank cmd parameter."; - return fail; + if (string.IsNullOrWhiteSpace(parameters["cmd"])) + return RestMissingParam("cmd"); + + TSRestPlayer tr = new TSRestPlayer(); + Commands.HandleCommand(tr, parameters["cmd"]); + return RestResponse(string.Join("\n", tr.GetCommandOutput())); } - private object Off(RestVerbs verbs, IParameterCollection parameters) + private object ServerOff(RestVerbs verbs, IParameterCollection parameters) { - bool confirm; - bool.TryParse(parameters["confirm"], out confirm); - bool nosave; - bool.TryParse(parameters["nosave"], out nosave); + if (!GetBool(parameters["confirm"], false)) + return RestInvalidParam("confirm"); - if (confirm == true) + if (!GetBool(parameters["nosave"], false)) + WorldGen.saveWorld(); + Netplay.disconnect = true; + + // Inform players the server is shutting down + var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; + foreach (TSPlayer player in TShock.Players.Where(p => null != p)) { - if (!nosave) - WorldGen.saveWorld(); - Netplay.disconnect = true; - RestObject reply = new RestObject("200"); - reply["response"] = "The server is shutting down."; - return reply; + TShock.Utils.ForceKick(player, msg); } - RestObject fail = new RestObject("400"); - fail["response"] = "Invalid/missing confirm switch, and/or missing nosave switch."; - return fail; + return RestResponse("The server is shutting down"); } - private object Broadcast(RestVerbs verbs, IParameterCollection parameters) + private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters) { - if (parameters["msg"] != null && parameters["msg"].Trim() != "") - { - TShock.Utils.Broadcast(parameters["msg"]); - RestObject reply = new RestObject("200"); - reply["response"] = "The message was broadcasted successfully."; - return reply; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Broadcast failed."; - return fail; + var msg = parameters["msg"]; + if (string.IsNullOrWhiteSpace(msg)) + return RestMissingParam("msg"); + TShock.Utils.Broadcast(msg); + return RestResponse("The message was broadcasted successfully"); } - #endregion - - #region RestMethods - - private object TokenTest(RestVerbs verbs, IParameterCollection parameters) - { - return new Dictionary - {{"status", "200"}, {"response", "Token is valid and was passed through correctly."}}; - } - - private object Status(RestVerbs verbs, IParameterCollection parameters) + private object ServerStatus(RestVerbs verbs, IParameterCollection parameters) { if (TShock.Config.EnableTokenEndpointAuthentication) - return new RestObject("403") {Error = "Server settings require a token for this API call."}; + return RestError("Server settings require a token for this API call"); - var activeplayers = Main.player.Where(p => p != null && p.active).ToList(); - string currentPlayers = string.Join(", ", activeplayers.Select(p => p.name)); + var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); + return new RestObject() + { + {"name", TShock.Config.ServerNickname}, + {"port", Convert.ToString(TShock.Config.ServerPort)}, + {"playercount", Convert.ToString(activeplayers.Count())}, + {"players", string.Join(", ", activeplayers.Select(p => p.name))}, + }; + } - var ret = new RestObject("200"); - ret["name"] = TShock.Config.ServerNickname; - ret["port"] = Convert.ToString(TShock.Config.ServerPort); - ret["playercount"] = Convert.ToString(activeplayers.Count()); - ret["players"] = currentPlayers; + private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters) + { + if (TShock.Config.EnableTokenEndpointAuthentication) + return RestError("Server settings require a token for this API call"); - ret["maxplayers"] = TShock.Config.MaxSlots; + var ret = new RestObject() + { + {"name", TShock.Config.ServerNickname}, + {"port", TShock.Config.ServerPort}, + {"playercount", Main.player.Where(p => null != p && p.active).Count()}, + {"maxplayers", TShock.Config.MaxSlots}, + {"world", Main.worldName} + }; + + if (GetBool(parameters["players"], false)) + { + var players = new ArrayList(); + foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) + { + var p = PlayerFilter(tsPlayer, parameters); + if (null != p) + players.Add(p); + } + ret.Add("players", players); + } + + if (GetBool(parameters["rules"], false)) + { + var rules = new Dictionary(); + rules.Add("AutoSave", TShock.Config.AutoSave); + rules.Add("DisableBuild", TShock.Config.DisableBuild); + rules.Add("DisableClownBombs", TShock.Config.DisableClownBombs); + rules.Add("DisableDungeonGuardian", TShock.Config.DisableDungeonGuardian); + rules.Add("DisableInvisPvP", TShock.Config.DisableInvisPvP); + rules.Add("DisableSnowBalls", TShock.Config.DisableSnowBalls); + rules.Add("DisableTombstones", TShock.Config.DisableTombstones); + rules.Add("EnableWhitelist", TShock.Config.EnableWhitelist); + rules.Add("HardcoreOnly", TShock.Config.HardcoreOnly); + rules.Add("PvPMode", TShock.Config.PvPMode); + rules.Add("SpawnProtection", TShock.Config.SpawnProtection); + rules.Add("SpawnProtectionRadius", TShock.Config.SpawnProtectionRadius); + + ret.Add("rules", rules); + } return ret; } + private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters) + { + return RestResponse("Token is valid and was passed through correctly"); + } + #endregion #region RestUserMethods + private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters) + { + return new RestObject() { { "activeusers", string.Join("\t", TShock.Players.Where(p => null != p && null != p.UserAccountName && p.Active).Select(p => p.UserAccountName)) } }; + } + private object UserListV2(RestVerbs verbs, IParameterCollection parameters) { - string playerlist = ""; - foreach (var TSPlayer in TShock.Players) + return new RestObject() { { "users", TShock.Users.GetUsers().Select(p => new Dictionary(){ + {"name", p.Name}, + {"id", p.ID}, + {"group", p.Group}, + {"ip", p.Address}, + }) } }; + } + + private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters) + { + var username = parameters["user"]; + if (string.IsNullOrWhiteSpace(username)) + return RestMissingParam("user"); + + var group = parameters["group"]; + if (string.IsNullOrWhiteSpace(group)) + return RestMissingParam("group"); + + var password = parameters["password"]; + if (string.IsNullOrWhiteSpace(password)) + return RestMissingParam("password"); + + // NOTE: ip can be blank + User user = new User(parameters["ip"], username, password, group); + try { - if (TSPlayer == null) - { - continue; - } - playerlist += playerlist == "" ? TSPlayer.UserAccountName : "\t" + TSPlayer.UserAccountName; + TShock.Users.AddUser(user); } - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("activeusers", playerlist); - return returnBlock; + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("User was successfully created"); } private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; + var password = parameters["password"]; var group = parameters["group"]; + if (string.IsNullOrWhiteSpace(group) && string.IsNullOrWhiteSpace(password)) + return RestMissingParam("group", "password"); - if (group == null && password == null) + User user = (User)ret; + var response = new RestObject(); + if (!string.IsNullOrWhiteSpace(password)) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "No parameters were passed."); - return returnBlock; + try + { + TShock.Users.SetUserPassword(user, password); + response.Add("password-response", "Password updated successfully"); + } + catch (Exception e) + { + return RestError("Failed to update user password (" + e.Message + ")"); + } } - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) + if (!string.IsNullOrWhiteSpace(group)) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specefied user doesn't exist."); - return returnBlock; + try + { + TShock.Users.SetUserGroup(user, group); + response.Add("group-response", "Group updated successfully"); + } + catch (Exception e) + { + return RestError("Failed to update user group (" + e.Message + ")"); + } } - if (password != null) - { - TShock.Users.SetUserPassword(user, password); - returnBlock.Add("password-response", "Password updated successfully."); - } - - if (group != null) - { - TShock.Users.SetUserGroup(user, group); - returnBlock.Add("group-response", "Group updated successfully."); - } - - returnBlock.Add("status", "200"); - return returnBlock; + return response; } private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters) { - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified user account does not exist."}}; - } - var returnBlock = new Dictionary(); + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; + try { - TShock.Users.RemoveUser(user); + TShock.Users.RemoveUser((User)ret); } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified user was unable to be removed."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "User deleted successfully."); - return returnBlock; + + return RestResponse("User deleted successfully"); } private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters) { - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified user account does not exist."}}; - } + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("group", user.Group); - returnBlock.Add("id", user.ID.ToString()); - return returnBlock; + User user = (User)ret; + return new RestObject() { { "group", user.Group }, { "id", user.ID.ToString() } }; } #endregion #region RestBanMethods - private object BanListIPs(RestVerbs verbs, IParameterCollection parameters) - { - RestObject returnItem = new RestObject("200"); - returnItem.Add("bans", TShock.Bans.GetBans()); - - return returnItem; - } - private object BanCreate(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); var ip = parameters["ip"]; var name = parameters["name"]; - var reason = parameters["reason"]; - if (ip == null && name == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Required parameters were missing from this API endpoint."); - return returnBlock; - } - - if (ip == null) - { - ip = ""; - } - - if (name == null) - { - name = ""; - } - - if (reason == null) - { - reason = ""; - } + if (string.IsNullOrWhiteSpace(ip) && string.IsNullOrWhiteSpace(name)) + return RestMissingParam("ip", "name"); try { - TShock.Bans.AddBan(ip, name, reason); + TShock.Bans.AddBan(ip, name, parameters["reason"], true); } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified ban was unable to be created."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Ban created successfully."); - return returnBlock; + return RestResponse("Ban created successfully"); } private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - - var type = parameters["type"]; - if (type == null) - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - var ban = new Ban(); - if (type == "ip") ban = TShock.Bans.GetBanByIp(parameters["user"]); - else if (type == "name") ban = TShock.Bans.GetBanByName(parameters["user"]); - else - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - if (ban == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified ban does not exist."}}; - } + var ret = BanFind(parameters); + if (ret is RestObject) + return ret; try { - TShock.Bans.RemoveBan(ban.IP); + Ban ban = (Ban)ret; + switch (parameters["type"]) + { + case "ip": + if (!TShock.Bans.RemoveBan(ban.IP, false, false, true)) + return RestResponse("Failed to delete ban (already deleted?)"); + break; + case "name": + if (!TShock.Bans.RemoveBan(ban.Name, true, GetBool(parameters["caseinsensitive"], true))) + return RestResponse("Failed to delete ban (already deleted?)"); + break; + default: + return RestError("Invalid Type: '" + parameters["type"] + "'"); + } + } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified ban was unable to be removed."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Ban deleted successfully."); - return returnBlock; + + return RestResponse("Ban deleted successfully"); } private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); + var ret = BanFind(parameters); + if (ret is RestObject) + return ret; - var type = parameters["type"]; - if (type == null) + Ban ban = (Ban)ret; + return new RestObject() { + {"name", null == ban.Name ? "" : ban.Name}, + {"ip", null == ban.IP ? "" : ban.IP}, + {"reason", null == ban.Reason ? "" : ban.Reason}, + }; + } + + private object BanListV2(RestVerbs verbs, IParameterCollection parameters) + { + var banList = new ArrayList(); + foreach (var ban in TShock.Bans.GetBans()) { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; + banList.Add( + new Dictionary + { + {"name", null == ban.Name ? "" : ban.Name}, + {"ip", null == ban.IP ? "" : ban.IP}, + {"reason", null == ban.Reason ? "" : ban.Reason}, + } + ); } - var ban = new Ban(); - if (type == "ip") ban = TShock.Bans.GetBanByIp(parameters["user"]); - else if (type == "name") ban = TShock.Bans.GetBanByName(parameters["user"]); - else - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - if (ban == null) - { - return new Dictionary { { "status", "400" }, { "error", "The specified ban does not exist." } }; - } - - returnBlock.Add("status", "200"); - returnBlock.Add("name", ban.Name); - returnBlock.Add("ip", ban.IP); - returnBlock.Add("reason", ban.Reason); - return returnBlock; + return new RestObject() { { "bans", banList } }; } #endregion #region RestWorldMethods - private object ChangeWorldSaveSettings(RestVerbs verbs, IParameterCollection parameters) + private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters) { - bool state; - bool.TryParse(verbs["state"], out state); + bool autoSave; + if (!bool.TryParse(verbs["bool"], out autoSave)) + return RestInvalidParam("state"); + TShock.Config.AutoSave = autoSave; - if (state == true) - { - TShock.Config.AutoSave = true; - } - else - { - TShock.Config.AutoSave = false; - } - - RestObject rj = new RestObject("200"); - rj["response"] = "Value changed"; - rj["state"] = state; - - return rj; + return RestResponse("AutoSave has been set to " + autoSave); } private object WorldSave(RestVerbs verbs, IParameterCollection parameters) { TShock.Utils.SaveWorld(); - RestObject rj = new RestObject("200"); - rj["response"] = "World saved."; - return rj; + return RestResponse("World saved"); } - private object Butcher(RestVerbs verbs, IParameterCollection parameters) + private object WorldButcher(RestVerbs verbs, IParameterCollection parameters) { bool killFriendly; if (!bool.TryParse(parameters["killfriendly"], out killFriendly)) - { - RestObject fail = new RestObject("400"); - fail["response"] = "The given value for killfriendly wasn't a boolean value."; - return fail; - } + return RestInvalidParam("killfriendly"); + if (killFriendly) - { killFriendly = !killFriendly; - } int killcount = 0; for (int i = 0; i < Main.npc.Length; i++) @@ -436,54 +435,38 @@ namespace TShockAPI } } - RestObject rj = new RestObject("200"); - rj["response"] = killcount + " NPCs have been killed."; - return rj; + return RestResponse(killcount + " NPCs have been killed"); } private object WorldRead(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("name", Main.worldName); - returnBlock.Add("size", Main.maxTilesX + "*" + Main.maxTilesY); - returnBlock.Add("time", Main.time); - returnBlock.Add("daytime", Main.dayTime); - returnBlock.Add("bloodmoon", Main.bloodMoon); - returnBlock.Add("invasionsize", Main.invasionSize); - return returnBlock; + return new RestObject() + { + {"name", Main.worldName}, + {"size", Main.maxTilesX + "*" + Main.maxTilesY}, + {"time", Main.time}, + {"daytime", Main.dayTime}, + {"bloodmoon", Main.bloodMoon}, + {"invasionsize", Main.invasionSize} + }; } private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters) { - if (WorldGen.genRand == null) + if (null == WorldGen.genRand) WorldGen.genRand = new Random(); WorldGen.dropMeteor(); - var returnBlock = new Dictionary {{"status", "200"}, {"response", "Meteor has been spawned."}}; - return returnBlock; + return RestResponse("Meteor has been spawned"); } private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var bloodmoonVerb = verbs["bool"]; bool bloodmoon; - if (bloodmoonVerb == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "No parameter was passed."); - return returnBlock; - } - if (!bool.TryParse(bloodmoonVerb, out bloodmoon)) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Unable to parse parameter."); - return returnBlock; - } + if (!bool.TryParse(verbs["bool"], out bloodmoon)) + return RestInvalidParam("bloodmoon"); Main.bloodMoon = bloodmoon; - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Blood Moon has been set to " + bloodmoon); - return returnBlock; + + return RestResponse("Blood Moon has been set to " + bloodmoon); } #endregion @@ -492,190 +475,342 @@ namespace TShockAPI private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - player.mute = false; - player.SendMessage("You have been remotely unmuted."); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was muted."); - } - return returnBlock; + return PlayerSetMute(parameters, false); } private object PlayerMute(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - player.mute = true; - player.SendMessage("You have been remotely muted."); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was muted."); - } - return returnBlock; - } - - private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) - { - RestObject returnBlock = new RestObject("200"); - returnBlock.Add("players", TShock.Players.Where(p => p != null && p.Active)); - return returnBlock; + return PlayerSetMute(parameters, true); } private object PlayerList(RestVerbs verbs, IParameterCollection parameters) { - var activeplayers = Main.player.Where(p => p != null && p.active).ToList(); - string currentPlayers = string.Join(", ", activeplayers.Select(p => p.name)); - var ret = new RestObject("200"); - ret["players"] = currentPlayers; - ret.Add("deprecated", "This endpoint is deprecated and has been replaced with /v2/lists/players."); - return ret; + var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); + return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; + } + + private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) + { + var playerList = new ArrayList(); + foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) + { + var p = PlayerFilter(tsPlayer, parameters); + if (null != p) + playerList.Add(p); + } + return new RestObject() { { "players", playerList } }; } private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - if (found.Count == 0) + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + var activeItems = player.TPlayer.inventory.Where(p => p.active).ToList(); + return new RestObject() { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - returnBlock.Add("status", "200"); - returnBlock.Add("nickname", player.Name); - returnBlock.Add("username", player.UserAccountName == null ? "" : player.UserAccountName); - returnBlock.Add("ip", player.IP); - returnBlock.Add("group", player.Group.Name); - returnBlock.Add("position", player.TileX + "," + player.TileY); - var activeItems = player.TPlayer.inventory.Where(p => p.active).ToList(); - returnBlock.Add("inventory", string.Join(", ", activeItems.Select(p => (p.name + ":" + p.stack)))); - returnBlock.Add("buffs", string.Join(", ", player.TPlayer.buffType)); - } - return returnBlock; + {"nickname", player.Name}, + {"username", null == player.UserAccountName ? "" : player.UserAccountName}, + {"ip", player.IP}, + {"group", player.Group.Name}, + {"position", player.TileX + "," + player.TileY}, + {"inventory", string.Join(", ", activeItems.Select(p => (p.name + ":" + p.stack)))}, + {"buffs", string.Join(", ", player.TPlayer.buffType)} + }; } private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - if (playerParam == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Unspecified name."); - return returnBlock; - } - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - TShock.Utils.ForceKick(player, reason == null ? "Kicked via web" : reason); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was kicked"); - } - return returnBlock; + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + TShock.Utils.ForceKick(player, null == parameters["reason"] ? "Kicked via web" : parameters["reason"]); + return RestResponse("Player " + player.Name + " was kicked"); } private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - TShock.Bans.AddBan(player.IP, player.Name, reason == null ? "Banned via web" : reason); - TShock.Utils.ForceKick(player, reason == null ? "Banned via web" : reason); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was banned"); - } - return returnBlock; + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + var reason = null == parameters["reason"] ? "Banned via web" : parameters["reason"]; + TShock.Bans.AddBan(player.IP, player.Name, reason); + TShock.Utils.ForceKick(player, reason); + return RestResponse("Player " + player.Name + " was banned"); } private object PlayerKill(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var from = verbs["from"]; - if (found.Count == 0) + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + player.DamagePlayer(999999); + var from = string.IsNullOrWhiteSpace(parameters["from"]) ? "Server Admin" : parameters["from"]; + player.SendMessage(string.Format("{0} just killed you!", from)); + return RestResponse("Player " + player.Name + " was killed"); + } + + #endregion + + #region RestGroupMethods + + private object GroupList(RestVerbs verbs, IParameterCollection parameters) + { + var groups = new ArrayList(); + foreach (Group group in TShock.Groups) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); + groups.Add(new Dictionary {{"name", group.Name}, {"parent", group.ParentName}, {"chatcolor", group.ChatColor}}); } - else if (found.Count > 1) + return new RestObject() { { "groups", groups } }; + } + + private object GroupInfo(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + return new RestObject() { + {"name", group.Name}, + {"parent", group.ParentName}, + {"chatcolor", group.ChatColor}, + {"permissions", group.permissions}, + {"negatedpermissions", group.negatedpermissions} + }; + } + + private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + try { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); + TShock.Groups.DeleteGroup(group.Name, true); } - else if (found.Count == 1) + catch (Exception e) { - var player = found[0]; - player.DamagePlayer(999999); - player.SendMessage(string.Format("{0} just killed you!", from)); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was killed."); + return RestError(e.Message); } - return returnBlock; + + return RestResponse("Group '" + group.Name + "' deleted successfully"); + } + + private object GroupCreate(RestVerbs verbs, IParameterCollection parameters) + { + var name = parameters["group"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("group"); + try + { + TShock.Groups.AddGroup(name, parameters["parent"], parameters["permissions"], parameters["chatcolor"], true); + } + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("Group '" + name + "' created successfully"); + } + + private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + var parent = (null == parameters["parent"]) ? group.ParentName : parameters["parent"]; + var chatcolor = (null == parameters["chatcolor"]) ? group.ChatColor : parameters["chatcolor"]; + var permissions = (null == parameters["permissions"]) ? group.Permissions : parameters["permissions"]; + try + { + TShock.Groups.UpdateGroup(group.Name, parent, permissions, chatcolor); + } + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("Group '" + group.Name + "' updated successfully"); + } + + #endregion + + #region Utility Methods + + private RestObject RestError(string message, string status = "400") + { + return new RestObject(status) {Error = message}; + } + + private RestObject RestResponse(string message, string status = "200") + { + return new RestObject(status) {Response = message}; + } + + private RestObject RestMissingParam(string var) + { + return RestError("Missing or empty " + var + " parameter"); + } + + private RestObject RestMissingParam(params string[] vars) + { + return RestMissingParam(string.Join(", ", vars)); + } + + private RestObject RestInvalidParam(string var) + { + return RestError("Missing or invalid " + var + " parameter"); + } + + private bool GetBool(string val, bool def) + { + bool ret; + return bool.TryParse(val, out ret) ? ret : def; + } + + private object PlayerFind(IParameterCollection parameters) + { + string name = parameters["player"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("player"); + + var found = TShock.Utils.FindPlayer(name); + switch(found.Count) + { + case 1: + return found[0]; + case 0: + return RestError("Player " + name + " was not found"); + default: + return RestError("Player " + name + " matches " + found.Count + " players"); + } + } + + private object UserFind(IParameterCollection parameters) + { + string name = parameters["user"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("user"); + + User user; + string type = parameters["type"]; + try + { + switch (type) + { + case null: + case "name": + type = "name"; + user = TShock.Users.GetUserByName(name); + break; + case "id": + user = TShock.Users.GetUserByID(Convert.ToInt32(name)); + break; + case "ip": + user = TShock.Users.GetUserByIP(name); + + break; + default: + return RestError("Invalid Type: '" + type + "'"); + } + } + catch (Exception e) + { + return RestError(e.Message); + } + + if (null == user) + return RestError(String.Format("User {0} '{1}' doesn't exist", type, name)); + + return user; + } + + private object BanFind(IParameterCollection parameters) + { + string name = parameters["ban"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("ban"); + + string type = parameters["type"]; + if (string.IsNullOrWhiteSpace(type)) + return RestMissingParam("type"); + + Ban ban; + switch (type) + { + case "ip": + ban = TShock.Bans.GetBanByIp(name); + break; + case "name": + ban = TShock.Bans.GetBanByName(name, GetBool(parameters["caseinsensitive"], true)); + break; + default: + return RestError("Invalid Type: '" + type + "'"); + } + + if (null == ban) + return RestError("Ban " + type + " '" + name + "' doesn't exist"); + + return ban; + } + + private object GroupFind(IParameterCollection parameters) + { + var name = parameters["group"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("group"); + + var group = TShock.Groups.GetGroupByName(name); + if (null == group) + return RestError("Group '" + name + "' doesn't exist"); + + return group; + } + + private Dictionary PlayerFilter(TSPlayer tsPlayer, IParameterCollection parameters) + { + var player = new Dictionary + { + {"nickname", tsPlayer.Name}, + {"username", null == tsPlayer.UserAccountName ? "" : tsPlayer.UserAccountName}, + {"ip", tsPlayer.IP}, + {"group", tsPlayer.Group.Name}, + {"active", tsPlayer.Active}, + {"state", tsPlayer.State}, + {"team", tsPlayer.Team}, + }; + foreach (IParameter filter in parameters) + { + if (player.ContainsKey(filter.Name) && !player[filter.Name].Equals(filter.Value)) + return null; + } + return player; + } + + private object PlayerSetMute(IParameterCollection parameters, bool mute) + { + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + player.mute = mute; + var verb = mute ? "muted" : "unmuted"; + player.SendMessage("You have been remotely " + verb); + return RestResponse("Player " + player.Name + " was " + verb); } #endregion diff --git a/TShockAPI/Rest/RestObject.cs b/TShockAPI/Rest/RestObject.cs index e63233dc..130e2ec8 100644 --- a/TShockAPI/Rest/RestObject.cs +++ b/TShockAPI/Rest/RestObject.cs @@ -41,7 +41,14 @@ namespace Rests set { this["response"] = value; } } - public RestObject(string status = "200") + // Parameterless constructor for deseralisation required by JavaScriptSerializer.Deserialize in TShockRestTestPlugin + // Note: The constructor with all defaults isn't good enough :( + public RestObject() + { + Status = "200"; + } + + public RestObject(string status = "200") { Status = status; } diff --git a/TShockRestTestPlugin/Properties/AssemblyInfo.cs b/TShockRestTestPlugin/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..17cb9a46 --- /dev/null +++ b/TShockRestTestPlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClassLibrary1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Multiplay")] +[assembly: AssemblyProduct("ClassLibrary1")] +[assembly: AssemblyCopyright("Copyright © Multiplay 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c6aed7ee-6282-49a2-8177-b79cad20d6d3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TShockRestTestPlugin/TShockRestTestPlugin.cs b/TShockRestTestPlugin/TShockRestTestPlugin.cs new file mode 100644 index 00000000..403b6f8f --- /dev/null +++ b/TShockRestTestPlugin/TShockRestTestPlugin.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Web; +using System.Web.Script.Serialization; +using System.Text.RegularExpressions; +using Microsoft.VisualStudio.TestTools.WebTesting; +using Microsoft.VisualStudio.TestTools.WebTesting.Rules; +using Rests; + +namespace TshockRestTestPlugin +{ + [DisplayName("JSON Status")] + [Description("Checks to see the that the JSON response has the specified status response")] + public class JsonValidateStatus : JsonValidate + { + public override void Validate(object sender, ValidationEventArgs e) + { + if (null != ValidateJson(sender, e)) + e.IsValid = true; + } + } + + [DisplayName("JSON Regexp Property")] + [Description("Checks to see the that the JSON response contains the specified property and is matches the specified regexp")] + public class JsonValidateRegexpProperty : JsonValidateProperty + { + // The name of the desired JSON property + [DisplayName("Regexp")] + [DefaultValue(true)] + public new bool UseRegularExpression { get { return base.UseRegularExpression; } set { base.UseRegularExpression = value; } } + } + + [DisplayName("JSON Error")] + [Description("Checks to see the that the JSON response contains the specified error")] + public class JsonValidateError : JsonValidateProperty + { + // The status of the JSON request + [DisplayName("JSON Status")] + [DefaultValue("400")] + public new string JSonStatus { get { return base.JSonStatus; } set { base.JSonStatus = value; } } + + // The name of the desired JSON property + [DisplayName("Property")] + [DefaultValue("error")] + public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } } + } + + [DisplayName("JSON Missing Parameter")] + [Description("Checks to see the that the JSON response indicates a missing or invalid parameter")] + public class JsonValidateMissingParameter : JsonValidateError + { + // The value of the desired JSON property + [DisplayName("Missing Value")] + public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or empty {0} parameter", value); } } + } + + [DisplayName("JSON Invalid Parameter")] + [Description("Checks to see the that the JSON response indicates a missing or invalid parameter")] + public class JsonValidateInvalidParameter : JsonValidateError + { + // The value of the desired JSON property + [DisplayName("Invalid Value")] + public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or invalid {0} parameter", value); } } + } + + [DisplayName("JSON Response")] + [Description("Checks to see the that the JSON response contains the specified message")] + public class JsonValidateResponse : JsonValidateProperty + { + // The name of the desired JSON property + [DisplayName("Response")] + [DefaultValue("response")] + public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } } + } + + [DisplayName("JSON Property")] + [Description("Checks to see the that the JSON response contains the specified property and is set to the specified value")] + public class JsonValidateProperty : JsonValidate + { + // The name of the desired JSON property + [DisplayName("Property")] + public string PropertyName { get; set; } + + // The value of the desired JSON property + [DisplayName("Value")] + public string PropertyValue { get; set; } + + // Is the value a regexp of the desired JSON property + [DisplayName("Regexp")] + [DefaultValue(false)] + public bool UseRegularExpression { get; set; } + + public override void Validate(object sender, ValidationEventArgs e) + { + RestObject response = ValidateJson(sender, e); + if (null == response) + return; + + if (null == response[PropertyName]) + { + e.Message = String.Format("{0} Not Found", PropertyName); + e.IsValid = false; + return; + } + + if (UseRegularExpression) + { + var re = new Regex(PropertyValue); + if (!re.IsMatch((string)response[PropertyName])) + { + e.Message = String.Format("{0} => '{1}' !~ '{2}'", PropertyName, response[PropertyName], PropertyValue); + e.IsValid = false; + return; + } + } + else + { + if (PropertyValue != (string)response[PropertyName]) + { + e.Message = String.Format("{0} => '{1}' != '{2}'", PropertyName, response[PropertyName], PropertyValue); + e.IsValid = false; + return; + } + } + + e.IsValid = true; + //e.WebTest.Context.Add(ContextParameterName, propertyValue); + } + } + + [DisplayName("JSON Has Properties")] + [Description("Checks to see the that the JSON response contains the specified properties (comma seperated)")] + public class JsonHasProperties : JsonValidate + { + // The name of the desired JSON properties to check + [DisplayName("Properties")] + [Description("A comma seperated list of property names to check exist")] + public string PropertyNames { get; set; } + + //--------------------------------------------------------------------- + public override void Validate(object sender, ValidationEventArgs e) + { + RestObject response = ValidateJson(sender, e); + if (null == response) + return; + foreach (var p in PropertyNames.Split(',')) + { + if (null == response[p]) + { + e.Message = String.Format("'{0}' Not Found", p); + e.IsValid = false; + return; + } + } + e.IsValid = true; + + //e.WebTest.Context.Add(ContextParameterName, propertyValue); + } + } + + public abstract class JsonValidate : ValidationRule + { + // The status of the JSON request + [DisplayName("JSON Status")] + [DefaultValue("200")] + public string JSonStatus { get; set; } + + public RestObject ValidateJson(object sender, ValidationEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.Response.BodyString)) + { + e.IsValid = false; + e.Message = String.Format("Empty or null response {0}", e.Response.StatusCode); + return null; + } + JavaScriptSerializer serialiser = new JavaScriptSerializer(); + //dynamic data = serialiser.Deserialize(e.Response.BodyString); + RestObject response = serialiser.Deserialize(e.Response.BodyString); + + if (JSonStatus != response.Status) + { + e.IsValid = false; + e.Message = String.Format("Response Status '{0}' not '{1}'", response.Status, JSonStatus); + return null; + } + + return response; + } + } +} \ No newline at end of file diff --git a/TShockRestTestPlugin/TShockRestTestPlugin.csproj b/TShockRestTestPlugin/TShockRestTestPlugin.csproj new file mode 100644 index 00000000..bf2dd997 --- /dev/null +++ b/TShockRestTestPlugin/TShockRestTestPlugin.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {F2FEDAFB-58DE-4611-9168-A86112C346C7} + Library + Properties + TshockRestTestPlugin + TshockRestTestPlugin + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + {49606449-072B-4CF5-8088-AA49DA586694} + TShockAPI + + + + + \ No newline at end of file diff --git a/UnitTests/BanManagerTest.cs b/UnitTests/BanManagerTest.cs index bcd4146b..2f4daa67 100644 --- a/UnitTests/BanManagerTest.cs +++ b/UnitTests/BanManagerTest.cs @@ -61,11 +61,7 @@ namespace UnitTests public void FindBanTest() { Assert.IsNotNull(Bans.GetBanByIp("127.0.0.1")); - TShock.Config.EnableBanOnUsernames = true; Assert.IsNotNull(Bans.GetBanByName("BanTest")); - // Disabled this this for now as its currently expected behavour - //TShock.Config.EnableBanOnUsernames = false; - //Assert.IsNull(Bans.GetBanByName("BanTest")); } } } diff --git a/UnitTests/RestApiTests.webtest b/UnitTests/RestApiTests.webtest new file mode 100644 index 00000000..84057748 --- /dev/null +++ b/UnitTests/RestApiTests.webtest @@ -0,0 +1,1542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index d9b9c7f9..923856ac 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -49,6 +49,7 @@ + ..\SqlBins\Mono.Data.Sqlite.dll @@ -87,6 +88,10 @@ {49606449-072B-4CF5-8088-AA49DA586694} TShockAPI + + {F2FEDAFB-58DE-4611-9168-A86112C346C7} + TShockRestTestPlugin + @@ -102,6 +107,9 @@ Always + + Always +