PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Server/DeviceHive.Test/ResourceTest.cs

https://github.com/oryol/devicehive-.net
C# | 515 lines | 342 code | 46 blank | 127 comment | 6 complexity | 4abfb3ae0180e0082cc66e076b67512b MD5 | raw file
Possible License(s): MIT
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Configuration;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using Newtonsoft.Json.Linq;
  10. using NUnit.Framework;
  11. using NUnit.Framework.Constraints;
  12. namespace DeviceHive.Test
  13. {
  14. /// <summary>
  15. /// Represents base class for resource-based API tests
  16. /// </summary>
  17. public abstract class ResourceTest : AssertionHelper
  18. {
  19. private Stack<string> _resources;
  20. #region Protected Properties
  21. /// <summary>
  22. /// Gets resource relative URI
  23. /// </summary>
  24. protected string ResourceUri { get; set; }
  25. /// <summary>
  26. /// Gets JsonClient object
  27. /// </summary>
  28. protected internal JsonClient Client { get; private set; }
  29. /// <summary>
  30. /// Gets or sets expected status code for create POST call
  31. /// </summary>
  32. protected int ExpectedCreatedStatus { get; set; }
  33. /// <summary>
  34. /// Gets or sets expected status code for DELETE call
  35. /// </summary>
  36. protected int ExpectedDeletedStatus { get; set; }
  37. /// <summary>
  38. /// Gets or sets unexisting resource ID to test for 404 calls
  39. /// </summary>
  40. protected int UnexistingResourceID { get; set; }
  41. /// <summary>
  42. /// Gets or sets username of existing administrator account
  43. /// </summary>
  44. protected string ExistingAdminUsername { get; set; }
  45. /// <summary>
  46. /// Gets or sets password of existing administrator account
  47. /// </summary>
  48. protected string ExistingAdminPassword { get; set; }
  49. #endregion
  50. #region Constructor
  51. /// <summary>
  52. /// Default constructor
  53. /// </summary>
  54. /// <param name="resourceUri">Resource relative URI</param>
  55. public ResourceTest(string resourceUri)
  56. {
  57. if (string.IsNullOrEmpty(resourceUri))
  58. throw new ArgumentException("ResourceUri is null or empty!", "resourceUri");
  59. ResourceUri = resourceUri;
  60. Client = new JsonClient(ConfigurationManager.AppSettings["ApiUrl"]);
  61. ExpectedCreatedStatus = 201;
  62. ExpectedDeletedStatus = 204;
  63. UnexistingResourceID = 999999;
  64. ExistingAdminUsername = "ut";
  65. ExistingAdminPassword = "ut_165GxCl$$boTTc2";
  66. }
  67. #endregion
  68. #region Protected Methods
  69. /// <summary>
  70. /// Test set up method
  71. /// </summary>
  72. [SetUp]
  73. protected virtual void SetUp()
  74. {
  75. _resources = new Stack<string>();
  76. OnCreateDependencies();
  77. }
  78. /// <summary>
  79. /// Test tear down method
  80. /// Default implementation deletes all created resources
  81. /// </summary>
  82. [TearDown]
  83. protected virtual void TearDown()
  84. {
  85. foreach (var resource in _resources)
  86. {
  87. Client.Delete(resource, auth: Admin);
  88. }
  89. _resources = null;
  90. }
  91. /// <summary>
  92. /// Creates dependencies required to test current resource
  93. /// </summary>
  94. protected virtual void OnCreateDependencies()
  95. {
  96. }
  97. /// <summary>
  98. /// Gets list of resources from the server
  99. /// </summary>
  100. /// <param name="auth">Authorization info</param>
  101. /// <returns>JArray that represents server response</returns>
  102. protected virtual JArray Get(Authorization auth = null)
  103. {
  104. // invoke get
  105. var response = Client.Get(ResourceUri, auth: auth);
  106. // verify response object
  107. ExpectResponseStatus(response, 200);
  108. Expect(response.Json, Is.InstanceOf<JArray>());
  109. return (JArray)response.Json;
  110. }
  111. /// <summary>
  112. /// Gets list of resources from the server
  113. /// </summary>
  114. /// <param name="query">Optional query parameters</param>
  115. /// <param name="auth">Authorization info</param>
  116. /// <returns>JArray that represents server response</returns>
  117. protected virtual JArray Get(Dictionary<string, string> query, Authorization auth = null)
  118. {
  119. // invoke get
  120. var response = Client.Get(ResourceUri + "?" +
  121. string.Join("&", query.Select(q => string.Format("{0}={1}", q.Key, Uri.EscapeUriString(q.Value)))), auth: auth);
  122. // verify response object
  123. ExpectResponseStatus(response, 200);
  124. Expect(response.Json, Is.InstanceOf<JArray>());
  125. return (JArray)response.Json;
  126. }
  127. /// <summary>
  128. /// Gets resource from the server
  129. /// </summary>
  130. /// <param name="resource">Resource to get</param>
  131. /// <param name="auth">Authorization info</param>
  132. /// <returns>JObject that represents server response</returns>
  133. protected virtual JObject Get(object resource, Authorization auth = null)
  134. {
  135. // invoke get
  136. var resourceId = GetResourceId(resource);
  137. var response = Client.Get(ResourceUri + "/" + resourceId, auth: auth);
  138. // verify response object
  139. ExpectResponseStatus(response, 200);
  140. Expect(response.Json, Is.InstanceOf<JObject>());
  141. Expect(GetResourceId((JObject)response.Json), Is.EqualTo(resourceId.ToString()));
  142. return (JObject)response.Json;
  143. }
  144. /// <summary>
  145. /// Creates resource on the server
  146. /// </summary>
  147. /// <param name="resourceObject">Resource object to create</param>
  148. /// <param name="auth">Authorization info</param>
  149. /// <returns>JObject that represents server response</returns>
  150. protected virtual JObject Create(object resourceObject, Authorization auth = null)
  151. {
  152. // invoke create
  153. var response = Client.Post(ResourceUri, resourceObject, auth: auth);
  154. // verify response object
  155. ExpectResponseStatus(response, ExpectedCreatedStatus);
  156. Expect(response.Json, Is.InstanceOf<JObject>());
  157. RegisterForDeletion(ResourceUri + "/" + GetResourceId((JObject)response.Json));
  158. return (JObject)response.Json;
  159. }
  160. /// <summary>
  161. /// Updates resource on the server
  162. /// </summary>
  163. /// <param name="resource">Resource to update</param>
  164. /// <param name="resourceObject">Resource object to send as update</param>
  165. /// <param name="auth">Authorization info</param>
  166. /// <returns>JObject that represents server response</returns>
  167. protected virtual JObject Update(object resource, object resourceObject, Authorization auth = null)
  168. {
  169. // invoke update
  170. var resourceId = GetResourceId(resource);
  171. var response = Client.Put(ResourceUri + "/" + resourceId, resourceObject, auth: auth);
  172. // verify response object
  173. ExpectResponseStatus(response, 200);
  174. Expect(response.Json, Is.InstanceOf<JObject>());
  175. Expect(GetResourceId((JObject)response.Json), Is.EqualTo(resourceId.ToString()));
  176. return (JObject)response.Json;
  177. }
  178. /// <summary>
  179. /// Deletes resource on the server
  180. /// </summary>
  181. /// <param name="resource">Resource to delete</param>
  182. /// <param name="auth">Authorization info</param>
  183. protected virtual void Delete(object resource, Authorization auth = null)
  184. {
  185. // invoke delete
  186. var resourceId = GetResourceId(resource);
  187. var response = Client.Delete(ResourceUri + "/" + resourceId, auth: auth);
  188. // verify response object
  189. ExpectResponseStatus(response, ExpectedDeletedStatus);
  190. }
  191. /// <summary>
  192. /// Gets resource identifier
  193. /// </summary>
  194. /// <param name="resource">Resource identifier or JObject representing the resource</param>
  195. /// <returns>Resource identifier</returns>
  196. protected virtual string GetResourceId(object resource)
  197. {
  198. if (resource is JObject)
  199. {
  200. var id = (JValue)((JObject)resource)["id"];
  201. if (id == null)
  202. throw new ArgumentException("Resource is JObject but does not include id property!", "resourceId");
  203. return id.Value.ToString();
  204. }
  205. return resource.ToString();
  206. }
  207. /// <summary>
  208. /// Creates a user and returns corresponding Authorization object
  209. /// </summary>
  210. /// <param name="role">User role</param>
  211. /// <param name="networks">List of network resources to associate user with</param>
  212. /// <returns>Coresponding Authorization object</returns>
  213. protected Authorization CreateUser(int role, params object[] networks)
  214. {
  215. // assign login and password
  216. var login = "_ut_" + Guid.NewGuid().ToString();
  217. var password = "pwd";
  218. // create user
  219. var userResource = Client.Post("/user", new { login = login, password = password, role = role, status = 0 }, auth: Admin);
  220. Expect(userResource.Status, Is.EqualTo(ExpectedCreatedStatus));
  221. var userId = GetResourceId(userResource.Json);
  222. RegisterForDeletion("/user/" + userId);
  223. // create user/network association
  224. foreach (var network in networks ?? new object[] { })
  225. {
  226. var networkId = GetResourceId(network);
  227. var userNetworkResponse = Client.Put("/user/" + userId + "/network/" + networkId, new { }, auth: Admin);
  228. Expect(userNetworkResponse.Status, Is.EqualTo(200));
  229. RegisterForDeletion("/user/" + userId + "/network/" + networkId);
  230. }
  231. // return user authorization object
  232. return User(login, password);
  233. }
  234. /// <summary>
  235. /// Registers resource for deletion when the test ends
  236. /// </summary>
  237. /// <param name="resource">Full resource url</param>
  238. protected void RegisterForDeletion(string resource)
  239. {
  240. _resources.Push(resource);
  241. }
  242. /// <summary>
  243. /// Gets default authorization for admin user
  244. /// </summary>
  245. protected Authorization Admin
  246. {
  247. get { return new Authorization("User", ExistingAdminUsername, ExistingAdminPassword); }
  248. }
  249. /// <summary>
  250. /// Gets authorization for a user
  251. /// </summary>
  252. /// <param name="username">User login</param>
  253. /// <param name="password">User password</param>
  254. protected Authorization User(string login, string password)
  255. {
  256. return new Authorization("User", login, password);
  257. }
  258. /// <summary>
  259. /// Gets authorization for a device
  260. /// </summary>
  261. /// <param name="id">Device identifier</param>
  262. /// <param name="key">Device key</param>
  263. protected Authorization Device(string id, string key)
  264. {
  265. return new Authorization("Device", id, key);
  266. }
  267. /// <summary>
  268. /// Gets FailsWith constraint
  269. /// </summary>
  270. /// <param name="status">Expected status</param>
  271. /// <returns>ResponseFailsWithContraint object</returns>
  272. protected ResponseFailsWithContraint FailsWith(int status)
  273. {
  274. return new ResponseFailsWithContraint(status);
  275. }
  276. /// <summary>
  277. /// Gets Matches constraint
  278. /// </summary>
  279. /// <param name="expected">Expected object</param>
  280. /// <returns>ResponseMatchesContraint object</returns>
  281. protected ResponseMatchesContraint Matches(object expected)
  282. {
  283. return new ResponseMatchesContraint(expected);
  284. }
  285. #endregion
  286. #region Private Methods
  287. private void ExpectResponseStatus(JsonResponse response, int expected)
  288. {
  289. if (response.Status != expected)
  290. {
  291. throw new ResourceException(response.Status,
  292. string.Format("Invalid server response! Expected: {0}, Actual: {1}", expected, response.Status));
  293. }
  294. }
  295. #endregion
  296. }
  297. public class ResponseMatchesContraint : Constraint
  298. {
  299. private readonly Regex _timestampRegex = new Regex(@"\""\d{4}\-\d{2}\-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{1,6}\""");
  300. #region Public Properties
  301. public object Expected { get; private set; }
  302. public static object Timestamp
  303. {
  304. get { return new TimestampValue(); }
  305. }
  306. #endregion
  307. #region Constructor
  308. public ResponseMatchesContraint(object expected)
  309. {
  310. Expected = expected;
  311. }
  312. #endregion
  313. #region Constraint Members
  314. public override bool Matches(object actual)
  315. {
  316. this.actual = actual;
  317. var actualJson = (JToken)actual;
  318. if (actualJson == null)
  319. return false;
  320. return IsMatches(actualJson, Expected);
  321. }
  322. public override void WriteDescriptionTo(MessageWriter writer)
  323. {
  324. writer.Write(JToken.FromObject(Expected));
  325. }
  326. public override void WriteActualValueTo(MessageWriter writer)
  327. {
  328. writer.Write(actual);
  329. }
  330. #endregion
  331. #region Private Methods
  332. private bool IsMatches(JToken actual, object expected)
  333. {
  334. foreach (var property in expected.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
  335. {
  336. var expectedProperty = property.GetValue(expected, null);
  337. var actualProperty = actual[property.Name];
  338. if (expectedProperty is TimestampValue)
  339. {
  340. // a timestamp is expected
  341. var jvalue = actualProperty as JValue;
  342. if (jvalue == null || !(jvalue.Value is DateTime))
  343. return false;
  344. if (!_timestampRegex.IsMatch(jvalue.Parent.ToString()))
  345. return false;
  346. continue;
  347. }
  348. if (actualProperty is JValue)
  349. {
  350. if (expectedProperty is int)
  351. expectedProperty = (long)(int)expectedProperty;
  352. if (!object.Equals(((JValue)actualProperty).Value, expectedProperty))
  353. return false;
  354. }
  355. else if (actualProperty is JObject)
  356. {
  357. if (expectedProperty == null || expectedProperty.GetType().IsPrimitive)
  358. return false;
  359. if (!IsMatches(actualProperty, expectedProperty))
  360. return false;
  361. }
  362. else if (actualProperty is JArray)
  363. {
  364. if (expectedProperty == null || !(expectedProperty is IEnumerable))
  365. return false;
  366. foreach (var expectedItem in ((IEnumerable)expectedProperty))
  367. {
  368. if (!actualProperty.Any(ap => IsMatches(ap, expectedItem)))
  369. return false;
  370. }
  371. }
  372. else
  373. {
  374. // unexpected type
  375. return false;
  376. }
  377. }
  378. return true;
  379. }
  380. #endregion
  381. #region TimestampValue struct
  382. private struct TimestampValue
  383. {
  384. }
  385. #endregion
  386. }
  387. public class ResponseFailsWithContraint : Constraint
  388. {
  389. #region Public Properties
  390. public int Status { get; private set; }
  391. #endregion
  392. #region Constructor
  393. public ResponseFailsWithContraint(int status)
  394. {
  395. Status = status;
  396. }
  397. #endregion
  398. #region Constraint Members
  399. public override bool Matches(ActualValueDelegate del)
  400. {
  401. try
  402. {
  403. var response = del();
  404. actual = "SUCCESS";
  405. return false; // the delegate must throw a ResourceException to indicate failure
  406. }
  407. catch (ResourceException ex)
  408. {
  409. actual = ex.Status;
  410. return ex.Status == Status;
  411. }
  412. }
  413. public override bool Matches(object actual)
  414. {
  415. return false;
  416. }
  417. public override void WriteDescriptionTo(MessageWriter writer)
  418. {
  419. writer.Write(Status);
  420. }
  421. #endregion
  422. }
  423. public class ResourceException : Exception
  424. {
  425. #region Public Properties
  426. public int Status { get; private set; }
  427. #endregion
  428. #region Constructor
  429. public ResourceException(int status, string message)
  430. : base(message)
  431. {
  432. Status = status;
  433. }
  434. #endregion
  435. }
  436. }