diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config
new file mode 100644
index 0000000..67f8ea0
--- /dev/null
+++ b/.nuget/NuGet.Config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe
new file mode 100644
index 0000000..8f61340
Binary files /dev/null and b/.nuget/NuGet.exe differ
diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets
new file mode 100644
index 0000000..83fe906
--- /dev/null
+++ b/.nuget/NuGet.targets
@@ -0,0 +1,136 @@
+
+
+
+ $(MSBuildProjectDirectory)\..\
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
+ $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
+
+
+
+
+ $(SolutionDir).nuget
+ packages.config
+
+
+
+
+ $(NuGetToolsPath)\NuGet.exe
+ @(PackageSource)
+
+ "$(NuGetExePath)"
+ mono --runtime=v4.0.30319 $(NuGetExePath)
+
+ $(TargetDir.Trim('\\'))
+
+ -RequireConsent
+ -NonInteractive
+
+ "$(SolutionDir) "
+ "$(SolutionDir)"
+
+
+ $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
+ $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
+
+
+
+ RestorePackages;
+ $(BuildDependsOn);
+
+
+
+
+ $(BuildDependsOn);
+ BuildPackage;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TestRail.sln b/TestRail.sln
new file mode 100644
index 0000000..6d28958
--- /dev/null
+++ b/TestRail.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C7307807-F4CD-44C6-AB5D-BD5F38D575DF}"
+ ProjectSection(SolutionItems) = preProject
+ .nuget\NuGet.Config = .nuget\NuGet.Config
+ .nuget\NuGet.exe = .nuget\NuGet.exe
+ .nuget\NuGet.targets = .nuget\NuGet.targets
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRail", "TestRail\TestRail.csproj", "{68C04A3F-DA7C-4588-8AA8-11A01C13630B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {68C04A3F-DA7C-4588-8AA8-11A01C13630B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {68C04A3F-DA7C-4588-8AA8-11A01C13630B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {68C04A3F-DA7C-4588-8AA8-11A01C13630B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {68C04A3F-DA7C-4588-8AA8-11A01C13630B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/TestRail/CommandResult.cs b/TestRail/CommandResult.cs
new file mode 100644
index 0000000..5f94fac
--- /dev/null
+++ b/TestRail/CommandResult.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace TestRail
+{
+ /// represents the result of a command
+ public class CommandResult : CommandResult
+ {
+ /// constructor
+ /// true if the command was successful
+ /// result of the command
+ /// exception thrown by the command
+ public CommandResult(bool wasSuccessful, string result, Exception e = null) : base(wasSuccessful, result, e) { }
+ }
+
+ /// represents the result of a command
+ /// type of the result
+ public class CommandResult
+ {
+ /// true if the command was successful
+ public bool WasSuccessful { get; set; }
+ /// result of the command
+ public T Value { get; set; }
+ /// exception thrown by the command
+ public Exception Exception { get; set; }
+
+ /// parameterless constructor
+ public CommandResult()
+ {
+ WasSuccessful = false;
+ Value = default(T);
+ Exception = null;
+ }
+
+ /// constructor
+ /// true if the command was successful
+ /// result of the command
+ /// exception thrown by the command
+ public CommandResult(bool wasSuccessful, T result, Exception e=null)
+ {
+ WasSuccessful = wasSuccessful;
+ Value = result;
+ Exception = e;
+ }
+ }
+}
diff --git a/TestRail/DateTimeExtensions.cs b/TestRail/DateTimeExtensions.cs
new file mode 100644
index 0000000..a6de976
--- /dev/null
+++ b/TestRail/DateTimeExtensions.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace TestRail
+{
+ /// extension methods for the datetime class
+ public static class DateTimeExtensions
+ {
+ /// converts the date to a unix timestamp
+ /// a unix time stamp representing the birthday
+ public static double ToUnixTimestamp(this DateTime dt)
+ {
+ return dt.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
+ }
+
+ /// generates a datetime from a unix timestamp
+ /// a unix timestamp
+ /// datetime corresponding to the supplied unix timestamp
+ public static DateTime FromUnixTimeStamp(double timestamp)
+ {
+ return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(timestamp).ToLocalTime();
+ }
+ }
+}
diff --git a/TestRail/JsonUtility.cs b/TestRail/JsonUtility.cs
new file mode 100644
index 0000000..07fd790
--- /dev/null
+++ b/TestRail/JsonUtility.cs
@@ -0,0 +1,51 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+
+namespace TestRail
+{
+ ///
+ /// Helper class for Json Objects
+ ///
+ internal static class JsonUtility
+ {
+ /// Merge two Json Objects
+ /// object 1 to merge
+ /// object 2 to merge
+ /// a non null Json object (NOTE: may be empty)
+ internal static JObject Merge(JObject obj1, JObject obj2)
+ {
+ if (null == obj1)
+ {
+ obj1 = new JObject();
+ }
+
+ if (null != obj2)
+ {
+ JToken token = obj2.First;
+ while (null != token)
+ {
+ obj1.Add(token);
+ token = token.Next;
+ }
+ }
+ return obj1;
+ }
+
+ /// Converts a JArray into a List of type T
+ /// JArray to parse
+ /// returns a list of objects corresponding to the json, empty list if nothing exists
+ internal static List ConvertJArrayToList(JArray jarray, Func parse)
+ {
+ List list = new List();
+ if (null != jarray && null != parse)
+ {
+ foreach (JObject json in jarray)
+ {
+ list.Add(parse(json));
+ }
+ }
+ return list;
+ }
+ }
+}
diff --git a/TestRail/Properties/AssemblyInfo.cs b/TestRail/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..5782804
--- /dev/null
+++ b/TestRail/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("TestRail")]
+[assembly: AssemblyDescription("TestRail Client Library")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Zoosk")]
+[assembly: AssemblyProduct("TestRail Client Library for .NET")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[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(true)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("b91a1174-5709-412a-9e95-ccaad637454c")]
+
+// 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/TestRail/TestRail.csproj b/TestRail/TestRail.csproj
new file mode 100644
index 0000000..fe781a1
--- /dev/null
+++ b/TestRail/TestRail.csproj
@@ -0,0 +1,89 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {68C04A3F-DA7C-4588-8AA8-11A01C13630B}
+ Library
+ Properties
+ TestRail
+ TestRail
+ v4.5
+ 512
+
+ ..\
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 0
+ false
+ bin\Debug\TestRail.XML
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TestRail/TestRailClient.cs b/TestRail/TestRailClient.cs
new file mode 100644
index 0000000..61f2a44
--- /dev/null
+++ b/TestRail/TestRailClient.cs
@@ -0,0 +1,1117 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+using TestRail.Types;
+
+namespace TestRail
+{
+ /// client used to access test case data in testrail
+ public class TestRailClient
+ {
+ /// url for testrail
+ protected string _URL_;
+ /// testrail username
+ protected string _UserName_;
+ /// testrail password
+ protected string _Password_;
+ /// projects in the test rail database
+ public List Projects { get { return _Projects.Value; } }
+
+ /// called when the client sends an http request
+ public event EventHandler OnHTTPRequestSent = (s, e) => { };
+ /// called when the client receives an http response
+ public event EventHandler OnHTTPResponseReceived = (s, e) => { };
+ /// called when an operation fails
+ public event EventHandler OnOperationFailed = (s, e) => { };
+
+ /// event args for http request sent
+ public class HTTPRequestSentEventArgs : EventArgs
+ {
+ /// http method (GET, POST, PUT, DELETE, etc.)
+ public string Method;
+ /// uri
+ public Uri Uri;
+ /// post data
+ public string PostContent;
+
+ /// constructor
+ /// http method used
+ /// uri used
+ /// post content sent (if any)
+ public HTTPRequestSentEventArgs(string method, Uri uri, string postContent = null)
+ {
+ this.Method = method;
+ this.Uri = uri;
+ this.PostContent = postContent;
+ }
+ }
+
+ private Lazy> _Projects;
+
+ private Dictionary _PriorityIDToLevel { get { return _LazyPriorityIDToLevel.Value; } }
+
+ private Lazy> _LazyPriorityIDToLevel { get; set; }
+
+ #region Constants
+ protected const string _NODE_CASE_ = "case";
+ protected const string _NODE_CASES_ = "cases";
+ protected const string _NODE_CASE_TYPES_ = "case_types";
+ protected const string _NODE_CASE_FIELDS_ = "case_fields";
+ protected const string _NODE_MILESTONE_ = "milestone";
+ protected const string _NODE_MILESTONES_ = "milestones";
+ protected const string _NODE_PLAN_ = "plan";
+ protected const string _NODE_PLANS_ = "plans";
+ protected const string _NODE_PLAN_ENTRY_ = "plan_entry";
+ protected const string _NODE_PROJECT_ = "project";
+ protected const string _NODE_RESULTS_ = "results";
+ protected const string _NODE_RESULTS_FOR_CASE_ = "results_for_case";
+ protected const string _NODE_RUN_ = "run";
+ protected const string _NODE_RUNS_ = "runs";
+ protected const string _NODE_SECTION_ = "section";
+ protected const string _NODE_SECTIONS_ = "sections";
+ protected const string _NODE_SUITE_ = "suite";
+ protected const string _NODE_SUITES_ = "suites";
+ protected const string _NODE_TEST_ = "test";
+ protected const string _NODE_TESTS_ = "tests";
+ protected const string _NODE_USER_ = "user";
+ protected const string _NODE_USERS_ = "users";
+ #endregion Constants
+
+ #region Constructor
+ /// constructor
+ /// url for test rail
+ /// user name
+ /// password
+ public TestRailClient(string url, string username, string password)
+ {
+ _URL_ = url;
+ _UserName_ = username;
+ _Password_ = password;
+
+ _Projects = new Lazy>(() => GetProjects());
+
+ // set up the lazy loading of the priority dictionary (priority id to priority value)
+ _LazyPriorityIDToLevel = new Lazy>(() => _CreatePrioritiesDict());
+ }
+ #endregion Constructor
+
+ #region Public Methods
+
+ ///
+ /// Get the priority for the case if we can
+ ///
+ /// case to get the priority from
+ /// int value of priority if possible, null if not found
+ public int? GetPriorityForCase(Case c)
+ {
+ int? priority = null;
+ if (null != c && c.PriorityID.HasValue && null != _PriorityIDToLevel && _PriorityIDToLevel.ContainsKey(c.PriorityID.Value))
+ {
+ priority = _PriorityIDToLevel[c.PriorityID.Value];
+ }
+ return priority;
+ }
+
+ #region Add Commands
+ /// adds a result for a test
+ /// id of the test
+ /// status of the result
+ /// comment to log
+ /// version
+ /// time elapsed to complete the test
+ /// defects associated with the result
+ /// id of the user the result is assigned to
+ /// result of the command
+ public CommandResult AddResult(ulong testID, ResultStatus? status, string comment = null, string version = null,
+ TimeSpan? elapsed = null, string defects = null, ulong? assignedToID = null, JObject customs = null)
+ {
+ string uri = _CreateUri_(_CommandType_.add, "result", testID);
+ Result r = new Result() { TestID = testID, StatusID = (ulong?)status, Comment = comment, Version = version, Elapsed = elapsed, Defects = defects, AssignedToID = assignedToID };
+ JObject jsonParams = JsonUtility.Merge(r.GetJson(), customs);
+ return _SendCommand(uri, jsonParams);
+ }
+
+ /// creates a new test result for a test run and case combination
+ /// the id of the test run
+ /// the id of the test case
+ /// status of the result
+ /// comment to log
+ /// version
+ /// time elapsed to complete the test
+ /// defects associated with the result
+ /// id of the user the result is assigned to
+ ///
+ public CommandResult AddResultForCase(ulong runID, ulong caseID, ResultStatus? status, string comment = null, string version = null,
+ TimeSpan? elapsed = null, string defects = null, ulong? assignedToID = null, JObject customs = null)
+ {
+ string uri = _CreateUri_(_CommandType_.add, "result_for_case", runID, caseID);
+
+ Result r = new Result() { StatusID = (ulong?)status, Comment = comment, Version = version, Elapsed = elapsed, Defects = defects, AssignedToID = assignedToID };
+ JObject jsonParams = JsonUtility.Merge(r.GetJson(), customs);
+ //JObject jsonParams = JsonHelper.Merge(_CreateJsonForResult(status, comment, version, elapsed, defects, assignedToID), customs);
+ return _SendCommand(uri, jsonParams);
+
+ }
+
+ /// adds a run
+ /// id of the project
+ /// id of the suite
+ /// name of the run
+ /// description of the run
+ /// id of the milestone
+ /// id of the user the run should be assigned to
+ ///(optional)an array of case IDs for the custom case selection, if null, then will include all case ids from the suite
+ /// result of the command
+ public CommandResult AddRun(ulong projectID, ulong suiteID, string name, string description, ulong milestoneID, ulong? assignedToID = null, HashSet caseIDs = null)
+ {
+ bool includeAll = true;
+
+ // validates whether we are in include all or custom case selection mode
+ if (null != caseIDs)
+ {
+ bool atLeastOneCaseFoundInSuite = _CasesFoundInSuite(projectID, suiteID, caseIDs);
+ if (atLeastOneCaseFoundInSuite)
+ {
+ includeAll = false;
+ }
+ else
+ {
+ return new CommandResult(false, 0, new Exception("Case IDs not found in the Suite"));
+ }
+ }
+
+ string uri = _CreateUri_(_CommandType_.add, _NODE_RUN_, projectID);
+ Run r = new Run() { SuiteID = suiteID, Name = name, Description = description, MilestoneID = milestoneID, AssignedTo = assignedToID, IncludeAll = includeAll, CaseIDs = caseIDs };
+ return _SendCommand(uri, r.GetJson());
+ }
+
+ /// Add a case
+ /// section id to add the case to
+ /// title of the case
+ /// (optional)the ID of the case type
+ /// (optional)the id of the case priority
+ /// (optional)the estimate, e.g. "30s" or "1m 45s"
+ /// (optional)the ID of the milestone to link to the test case
+ /// (optional)a comma-separated list of references/requirements
+ /// result of the command
+ public CommandResult AddCase(ulong sectionID, string title, ulong? typeID = null, ulong? priorityID = null, string estimate = null, ulong? milestoneID = null, string refs = null)
+ {
+ return _AddCase_(sectionID, title, typeID, priorityID, estimate, milestoneID, refs, null);
+ }
+
+ /// Add a project
+ /// the name of the project
+ /// (optional)the description of the project
+ /// (optional)true if the announcement should be displayed on the project's overview page and false otherwise
+ /// result of the command
+ public CommandResult AddProject(string projectName, string announcement = null, bool? showAnnouncement = null)
+ {
+ if (string.IsNullOrWhiteSpace(projectName))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("projectName"));
+ }
+
+ string uri = _CreateUri_(_CommandType_.add, _NODE_PROJECT_);
+ Project p = new Project() { Name = projectName, Announcement = announcement, ShowAnnouncement = showAnnouncement };
+ return _SendCommand(uri, p.GetJson());
+ }
+
+ /// creates a new section
+ /// the ID of the project
+ /// the ID of the test suite
+ /// the name of the section
+ /// (optional)the ID of the parent section (to build section hierarchies)
+ /// result of the command
+ public CommandResult AddSection(ulong projectID, ulong suiteID, string name, ulong? parentID = null)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("name"));
+ }
+
+ string uri = _CreateUri_(_CommandType_.add, _NODE_SECTION_, projectID);
+ Section s = new Section() { SuiteID = suiteID, ParentID = parentID, Name = name };
+ return _SendCommand(uri, s.GetJson());
+ }
+
+ /// Creates a new test suite
+ /// the ID of the project the test suite should be added to
+ /// the name of the test suite
+ /// (optional)the description of the test suite
+ /// result of the command
+ public CommandResult AddSuite(ulong projectID, string name, string description = null)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("name"));
+ }
+
+ string uri = _CreateUri_(_CommandType_.add, _NODE_SUITE_, projectID);
+ Suite s = new Suite() { Name = name, Description = description };
+ return _SendCommand(uri, s.GetJson());
+ }
+
+ /// creates a new plan
+ /// id of the project the test plan should be added to
+ /// name of the test plan
+ /// (optional)description of the test plan
+ /// (optional)id of the milestone to link the test plan
+ /// an array of objects describing the test runs of the plan
+ /// result of the command
+ public CommandResult AddPlan(ulong projectID, string name, string description = null, ulong? milestoneID = null, List entries = null)
+ // , params ulong[] suiteIDs)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("name"));
+ }
+
+ string uri = _CreateUri_(_CommandType_.add, _NODE_PLAN_, projectID);
+ Plan p = new Plan() { Name = name, Description = description, MilestoneID = milestoneID, Entries = entries };
+ JObject jsonParams = p.GetJson();
+ return _SendCommand(uri, jsonParams);
+ }
+
+ /// Creates a new test run for a test plan
+ /// the ID of the plan the test run should be added to
+ /// the ID of the test suite for the test run
+ /// (optional)the name of the test run
+ /// (optional)the ID of the user the test run should be assigned to
+ /// (optional)true for including all test cases of the test suite and false for a custom selection (default: true)
+ ///
+ public CommandResult AddPlanEntry(ulong planID, ulong suiteID, string name = null, ulong? assignedToID = null, List caseIDs = null)
+ {
+ string uri = _CreateUri_(_CommandType_.add, _NODE_PLAN_ENTRY_, planID);
+ PlanEntry pe = new PlanEntry() { AssignedToID = assignedToID, SuiteID = suiteID, Name = name, CaseIDs = caseIDs };
+ JObject jsonParams = pe.GetJson();
+ return _SendCommand(uri, jsonParams);
+ }
+
+ /// adds a milestone
+ /// id of the project
+ /// name of the milestone
+ /// (optional)description of the milestone
+ /// (optional)date on which the milestone is due
+ /// result of the command
+ public CommandResult AddMilestone(ulong projectID, string name, string description = null, DateTime? dueOn = null)
+ {
+ string uri = _CreateUri_(_CommandType_.add, _NODE_MILESTONE_, projectID);
+ Milestone m = new Milestone() { Name = name, Description = description, DueOn = dueOn };
+ return _SendCommand(uri, m.GetJson());
+ }
+
+ #endregion Add Commands
+
+ #region Update Commands
+ /// update an existing case
+ /// the ID of the test case
+ /// title of the case
+ /// (optional)the ID of the case type
+ /// (optional)the id of the case priority
+ /// (optional)the estimate, e.g. "30s" or "1m 45s"
+ /// (optional)the ID of the milestone to link to the test case
+ /// (optional)a comma-separated list of references/requirements
+ /// result of the command
+ public CommandResult UpdateCase(ulong caseID, string title, ulong? typeID = null, ulong? priorityID = null, string estimate = null, ulong? milestoneID = null, string refs = null)
+ {
+ return _UpdateCase_(caseID, title, typeID, priorityID, estimate, milestoneID, refs, null);
+ }
+
+
+ /// update an existing milestone
+ /// id of the milestone
+ /// (optional)name of the milestone
+ /// (optional)description of the milestone
+ /// (optional)date on which the milestone is due
+ /// result of the command
+ public CommandResult UpdateMilestone(ulong milestoneID, string name = null, string description = null, DateTime? dueOn = null, bool? isCompleted = null)
+ {
+ string uri = _CreateUri_(_CommandType_.update, _NODE_MILESTONE_, milestoneID);
+ Milestone m = new Milestone() { Name = name, Description = description, DueOn = dueOn, IsCompleted = isCompleted };
+ return _SendCommand(uri, m.GetJson());
+ }
+
+ /// Update an existing plan
+ /// id of the plan
+ /// (optional)name of the test plan
+ /// (optional)the description of the test plan
+ /// (optional)the id of the milestone to link to the test plan
+ ///
+ public CommandResult UpdatePlan(ulong planID, string name = null, string description = null, ulong? milestoneID = null)
+ {
+ string uri = _CreateUri_(_CommandType_.update, _NODE_PLAN_, planID);
+ Plan p = new Plan() { Name = name, Description = description, MilestoneID = milestoneID };
+ JObject jsonParams = p.GetJson();
+ return _SendCommand(uri, jsonParams);
+ }
+
+ /// Creates a new test run for a test plan
+ /// the ID of the plan the test run should be added to
+ /// the ID of the test plan entry
+ /// (optional)the name of the test run
+ /// (optional)the ID of the user the test run should be assigned to
+ /// (optional)true for including all test cases of the test suite and false for a custom selection (default: true)
+ ///
+ public CommandResult UpdatePlanEntry(ulong planID, string entryID, string name = null, ulong? assignedToID = null, List caseIDs = null)
+ {
+ string uri = _CreateUri_(_CommandType_.update, _NODE_PLAN_ENTRY_, planID, null, null, entryID);
+ PlanEntry pe = new PlanEntry() { AssignedToID = assignedToID, Name = name, CaseIDs = caseIDs };
+ JObject jsonParams = pe.GetJson();
+ return _SendCommand(uri, jsonParams);
+ }
+
+ /// Update an existing project
+ /// the id of the project
+ /// the name of the project
+ /// (optional)the description of the project
+ /// (optional)true if the announcement should be displayed on the project's overview page and false otherwise
+ /// (optional)specifies whether a project is considered completed or not
+ ///
+ public CommandResult UpdateProject(ulong projectID, string projectName, string announcement = null, bool? showAnnouncement = null, bool? isCompleted = null)
+ {
+ string uri = _CreateUri_(_CommandType_.update, _NODE_PROJECT_, projectID);
+ Project p = new Project() { Name = projectName, Announcement = announcement, ShowAnnouncement = showAnnouncement, IsCompleted = isCompleted };
+ return _SendCommand(uri, p.GetJson());
+ }
+
+ /// update an existing test run
+ /// the id of an existing run
+ /// (optional)name of the test run
+ /// (optional)description of the test run
+ /// (optional)the id of the milestone to link to the test run
+ /// (optional)true for including all test cases of the test suite and false for a custom case selection
+ ///an array of case IDs for the custom case selection
+ ///
+ public CommandResult UpdateRun(ulong runID, string name = null, string description = null, ulong? milestoneID = null, HashSet caseIDs = null)
+ {
+ bool includeAll = true;
+ Run run = GetRun(runID);
+
+ // validates whether we are in include all or custom case selection mode
+ if (null != run && run.ProjectID.HasValue && run.SuiteID.HasValue && null != caseIDs)
+ {
+ bool atLeastOneCaseFoundInSuite = _CasesFoundInSuite(run.ProjectID.Value, run.SuiteID.Value, caseIDs);
+ if (atLeastOneCaseFoundInSuite)
+ {
+ includeAll = false;
+ }
+ else
+ {
+ return new CommandResult(false, 0, new Exception("Case IDs not found in the Suite"));
+ }
+ }
+
+ string uri = _CreateUri_(_CommandType_.update, _NODE_RUN_, runID);
+ Run r = new Run() { Name = name, Description = description, MilestoneID = milestoneID, IncludeAll = includeAll, CaseIDs = caseIDs };
+ return _SendCommand(uri, r.GetJson());
+ }
+
+
+ /// Updates an existing section
+ /// id of the section to update
+ /// name of the section
+ ///
+ public CommandResult UpdateSection(ulong sectionID, string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("name"));
+ }
+
+ string uri = _CreateUri_(_CommandType_.update, _NODE_SECTION_, sectionID);
+ Section s = new Section() { ID = sectionID, Name = name };
+ return _SendCommand(uri, s.GetJson());
+ }
+
+ /// Update an existing suite
+ /// id of the suite to update
+ /// (optional)new name to update to
+ /// (optional)new description to update to
+ ///
+ public CommandResult UpdateSuite(ulong suiteID, string name = null, string description = null)
+ {
+ string uri = _CreateUri_(_CommandType_.update, _NODE_SUITE_, suiteID);
+ Suite s = new Suite() { Name = name, Description = description };
+ return _SendCommand(uri, s.GetJson());
+ }
+ #endregion Update Commands
+
+ #region Close Commands
+ /// closes a plan
+ /// id of the plan
+ /// true if successful
+ public bool ClosePlan(ulong planID)
+ {
+ string uri = _CreateUri_(_CommandType_.close, _NODE_PLAN_, planID);
+
+ var result = _CallPostEndpoint(uri);
+ if (result.WasSuccessful)
+ {
+ JObject json = JObject.Parse(result.Value);
+ return result.WasSuccessful;
+ }
+
+ OnOperationFailed(this, "Could not close plan : " + result.Value);
+ return false;
+ }
+
+ /// closes a run
+ /// id of the run
+ /// true if successful
+ public bool CloseRun(ulong runID)
+ {
+ string uri = _CreateUri_(_CommandType_.close, _NODE_RUN_, runID);
+ var result = _CallPostEndpoint(uri);
+ // var result = _CallPostEndpoint("?/api/v2/close_run/" + runID.ToString());
+ if (result.WasSuccessful)
+ {
+ JObject json = JObject.Parse(result.Value);
+ return result.WasSuccessful;
+ }
+
+ OnOperationFailed(this, "Could not close run : " + result.Value);
+ return false;
+ }
+ #endregion Close Commands
+
+ #region Delete Commands
+ /// Delete a milestone
+ /// id of the milestone
+ /// result of the deletion
+ public CommandResult DeleteMilestone(ulong milestoneID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_MILESTONE_, milestoneID);
+ return _SendCommand(uri);
+ }
+
+ /// Delete a case
+ /// id of the case to delete
+ /// result of the deletion
+ public CommandResult DeleteCase(ulong caseID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_CASE_, caseID);
+ return _SendCommand(uri);
+ }
+
+ /// Delete a plan
+ /// id of the plan to delete
+ /// result of the deletion
+ public CommandResult DeletePlan(ulong planID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_PLAN_, planID);
+ return _SendCommand(uri);
+ }
+
+ /// Delete a specific plan entry for a plan id
+ /// id of the plan
+ /// string representation of the GUID for the entryID
+ /// result of the deletion
+ public CommandResult DeletePlanEntry(ulong planID, string entryID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_PLAN_ENTRY_, planID, null, null, entryID);
+ return _SendCommand(uri);
+ }
+
+ /// Delete the Project
+ /// id of the project to delete
+ /// result of the deletion
+ public CommandResult DeleteProject(ulong projectID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_PROJECT_, projectID);
+ return _SendCommand(uri);
+ }
+
+ /// Delete the section
+ /// id of the section to delete
+ /// result of the deletion
+ public CommandResult DeleteSection(ulong sectionID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_SECTION_, sectionID);
+ return _SendCommand(uri);
+ }
+
+ /// Delete the suite
+ /// id of the suite to delete
+ /// result of the deletion
+ public CommandResult DeleteSuite(ulong suiteID)
+ {
+ string uri = _CreateUri_(_CommandType_.delete, _NODE_SUITE_, suiteID);
+ return _SendCommand(uri);
+ }
+ #endregion Delete Commands
+
+ #region Get Commands
+ /// gets a test
+ /// id of the test
+ /// information about the test
+ public Test GetTest(ulong testID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_TEST_, testID);
+ return _GetItem_(_NODE_TEST_, uri, Test.Parse);
+ }
+
+ /// gets tests associated with a run
+ /// id of the run
+ /// tests associated with the run
+ public List GetTests(ulong runID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_TESTS_, runID);
+ return _GetItems_(_NODE_TESTS_, uri, Test.Parse);
+ }
+
+ /// gets a case
+ /// id of the case
+ /// information about the case
+ public Case GetCase(ulong caseID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_CASE_, caseID);
+ return _GetItem_(_NODE_CASE_, uri, Case.Parse);
+ }
+
+ /// gets cases associated with a suite
+ /// id of the project
+ /// id of the suite
+ /// (optional) id of the section
+ /// cases associated with the suite
+ public List GetCases(ulong projectID, ulong suiteID, ulong? sectionID = null)
+ {
+ string optionalSectionID = sectionID.HasValue ? string.Format("§ion_id={0}", sectionID.Value) : string.Empty;
+ string options = string.Format("&suite_id={0}{1}", suiteID, optionalSectionID);
+ string uri = _CreateUri_(_CommandType_.get, _NODE_CASES_, projectID, null, options);
+ return _GetItems_(_NODE_CASES_, uri, Case.Parse);
+ }
+
+ public List GetCaseFields()
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_CASE_FIELDS_);
+ return _GetItems_(_NODE_CASE_TYPES_, uri, CaseField.Parse);
+ }
+
+ public List GetCaseTypes()
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_CASE_TYPES_);
+ return _GetItems_(_NODE_CASE_TYPES_, uri, CaseType.Parse);
+ }
+
+ /// gets a suite
+ /// id of the suite
+ /// information about the suite
+ public Suite GetSuite(ulong suiteID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_SUITE_, suiteID);
+ return _GetItem_(_NODE_SUITE_, uri, Suite.Parse);
+ }
+
+ /// gets suites associated with a project
+ /// id of the project
+ /// suites associated with the project
+ public List GetSuites(ulong projectID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_SUITES_, projectID);
+ return _GetItems_(_NODE_SUITES_, uri, Suite.Parse);
+ }
+
+ /// gets a section
+ /// id of the section
+ /// information about the section
+ public Section GetSection(ulong sectionID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_SECTION_, sectionID);
+ return _GetItem_(_NODE_SECTION_, uri, Section.Parse);
+ }
+
+ /// gets sections associated with a suite
+ /// id of the project
+ /// id of the suite
+ /// sections associated with the suite
+ public List GetSections(ulong projectID, ulong suiteID)
+ {
+ string options = string.Format("&suite_id={0}", suiteID);
+ string uri = _CreateUri_(_CommandType_.get, _NODE_SECTIONS_, projectID, null, options);
+ return _GetItems_(_NODE_SECTIONS_, uri, Section.Parse);
+ }
+
+ /// gets a run
+ /// id of the run
+ /// information about the run
+ public Run GetRun(ulong runID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_RUN_, runID);
+ return _GetItem_(_NODE_RUN_, uri, Run.Parse);
+ }
+
+ /// gets runs associated with a project
+ /// id of the project
+ /// runs associated with the project
+ public List GetRuns(ulong projectID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_RUNS_, projectID);
+ return _GetItems_(_NODE_RUNS_, uri, Run.Parse);
+ }
+
+ /// gets a plan
+ /// id of the plan
+ /// information about the plan
+ public Plan GetPlan(ulong planID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_PLAN_, planID);
+ return _GetItem_(_NODE_PLAN_, uri, Plan.Parse);
+ }
+
+ /// gets plans associated with a project
+ /// id of the project
+ /// plans associated with the project
+ public List GetPlans(ulong projectID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_PLANS_, projectID);
+ return _GetItems_(_NODE_PLANS_, uri, Plan.Parse);
+ }
+
+ /// gets a milestone
+ /// id of the milestone
+ /// information about the milestone
+ public Milestone GetMilestone(ulong milestoneID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_MILESTONE_, milestoneID);
+ return _GetItem_(_NODE_MILESTONE_, uri, Milestone.Parse);
+ }
+
+ /// gets milestones associated with a project
+ /// id of the project
+ /// milestone associated with project
+ public List GetMilestones(ulong projectID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_MILESTONES_, projectID);
+ return _GetItems_(_NODE_MILESTONES_, uri, Milestone.Parse);
+ }
+
+ /// gets a project
+ /// id of the project
+ /// information about the project
+ public Project GetProject(ulong projectID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_PROJECT_, projectID);
+ return _GetItem_(_NODE_PROJECT_, uri, Project.Parse);
+ }
+
+ /// gets all projects contained in the testrail instance
+ ///
+ public List GetProjects()
+ {
+ string nodeName = "projects";
+ string uri = _CreateUri_(_CommandType_.get, nodeName);
+ return _GetItems_(nodeName, uri, Project.Parse);
+ }
+
+ /// Get User for user id
+ /// user id to search for
+ /// a User object
+ public User GetUser(ulong userID)
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_USER_, userID);
+ return _GetItem_(_NODE_USER_, uri, User.Parse);
+ }
+
+ /// Find a user by their email address
+ /// email address of the user
+ /// user if found
+ public User GetUserByEmail(string email)
+ {
+ // validate the email string
+ if (string.IsNullOrWhiteSpace(email))
+ {
+ return default(User);
+ }
+
+ string nodeName = "user_by_email";
+ string optional = string.Format("&email={0}", email);
+ string uri = _CreateUri_(_CommandType_.get, nodeName, null, null, optional);
+ return _GetItem_(nodeName, uri, User.Parse);
+ }
+
+ /// Get a list of users in the testrail instance
+ /// List of users
+ public List GetUsers()
+ {
+ string uri = _CreateUri_(_CommandType_.get, _NODE_USERS_);
+ return _GetItems_(_NODE_USERS_, uri, User.Parse);
+ }
+
+ ///
+ /// Returns a list of test results for a test
+ ///
+ /// id of the test
+ /// (optional) maximum amount of test results to return, latest first
+ ///
+ public List GetResults(ulong testID, ulong? limit = null)
+ {
+ string optional = (limit.HasValue) ? string.Format("&limit={0}", limit.Value) : string.Empty;
+ string uri = _CreateUri_(_CommandType_.get, _NODE_RESULTS_, testID, null, optional);
+ return _GetItems_(_NODE_RESULTS_, uri, Result.Parse);
+ }
+
+ ///
+ /// Return the list of test results for a test run and the case combination
+ ///
+ /// id of the rest run
+ /// id of the test case
+ /// (optional) maximum amount of test results to return, latest first
+ /// list of test results for a case
+ public List GetResultsForCase(ulong runID, ulong caseID, ulong? limit = null)
+ {
+ string optional = (limit.HasValue) ? string.Format("&limit={0}", limit.Value) : string.Empty;
+ string uri = _CreateUri_(_CommandType_.get, _NODE_RESULTS_FOR_CASE_, runID, caseID, optional);
+ return _GetItems_(_NODE_RESULTS_FOR_CASE_, uri, Result.Parse);
+ }
+
+ ///
+ /// Returns the list of statuses available to test rail
+ ///
+ /// list of possible statuses
+ public List GetStatuses()
+ {
+ string nodeName = "statuses";
+ string uri = _CreateUri_(_CommandType_.get, nodeName);
+ return _GetItems_(nodeName, uri, Status.Parse);
+ }
+
+ ///
+ /// Get a list of all available priorities
+ ///
+ /// list of priorities
+ public List GetPriorities()
+ {
+ string nodeName = "priorities";
+ string uri = _CreateUri_(_CommandType_.get, nodeName);
+ return _GetItems_(nodeName, uri, Priority.Parse);
+ }
+ #endregion Get Commands
+
+ #endregion Public Methods
+
+ #region Protected Methods
+ /// executes a get request for an item
+ /// the type of item
+ /// the name of item's node
+ /// a method which parse json into the item
+ /// the id of the item
+ /// object of the supplied type containing information about the item
+ protected T _GetItem_(string nodeName, string uri, Func parse)
+ where T : new()
+ {
+
+ var result = _CallTestRailGetEndpoint(uri);
+ if (!result.WasSuccessful)
+ {
+ OnOperationFailed(this, "Could not get " + nodeName + ": " + result.Value);
+ return default(T);
+ }
+
+ JObject json = JObject.Parse(result.Value);
+ return parse(json);
+ }
+
+ /// executes a get request for an item
+ /// the type of the item
+ /// the name of the item's node
+ /// a method which parses the json into the item
+ /// the id of the first item on which to filter the get request
+ /// the id of the second item on which to filter the get request
+ /// additional options to append to the get request
+ /// list of objects of the supplied type corresponding th supplied filters
+ protected List _GetItems_(string nodeName, string uri, Func parse)
+ where T : new()
+ {
+ List items = new List();
+ var result = _CallTestRailGetEndpoint(uri);
+
+ if (!result.WasSuccessful)
+ {
+ OnOperationFailed(this, "Could not get " + nodeName + "s: " + result.Value);
+ }
+ else
+ {
+ JArray jarray = JArray.Parse(result.Value);
+ if (null != jarray)
+ {
+ items = JsonUtility.ConvertJArrayToList(jarray, parse);
+ }
+ }
+ return items;
+ }
+
+ /// Creates a URI with the parameters given in the format
+ /// the type of action the server is going to take (i.e. get, add, update, close)
+ ///
+ ///
+ ///
+ ///
+ /// the uri
+ protected static string _CreateUri_(_CommandType_ uriType, string nodeName, ulong? id1 = null, ulong? id2 = null, string options = null, string id2Str = null)
+ {
+ string uri = string.Format("?/api/v2/{0}_{1}{2}{3}{4}",
+ uriType.ToString(),
+ nodeName,
+ (id1.HasValue ? "/" + id1.Value.ToString() : string.Empty),
+ (id2.HasValue ? "/" + id2.Value.ToString() : (!string.IsNullOrWhiteSpace(id2Str)) ? "/" + id2Str : string.Empty),
+ (!string.IsNullOrWhiteSpace(options) ? options : string.Empty));
+ return uri;
+ }
+
+ /// Add a case
+ /// section id to add the case to
+ /// title of the case
+ /// (optional)the ID of the case type
+ /// (optional)the id of the case priority
+ /// (optional)the estimate, e.g. "30s" or "1m 45s"
+ /// (optional)the ID of the milestone to link to the test case
+ /// (optional)a comma-separated list of references/requirements
+ /// (optional)custom json params to add to the current json parameters
+ /// result of the command
+ protected CommandResult _AddCase_(ulong sectionID, string title, ulong? typeID = null, ulong? priorityID = null, string estimate = null, ulong? milestoneID = null, string refs = null, JObject customs = null)
+ {
+ if (string.IsNullOrWhiteSpace(title))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("title"));
+ }
+ string uri = _CreateUri_(_CommandType_.add, _NODE_CASE_, sectionID);
+ Case tmpCase = new Case() { Title = title, TypeID = typeID, PriorityID = priorityID, Estimate = estimate, MilestoneID = milestoneID, References = refs };
+ JObject jsonParams = JsonUtility.Merge(tmpCase.GetJson(), customs);
+ return _SendCommand(uri, jsonParams);
+ }
+
+ /// update an existing case
+ /// the ID of the test case
+ /// title of the case
+ /// (optional)the ID of the case type
+ /// (optional)the id of the case priority
+ /// (optional)the estimate, e.g. "30s" or "1m 45s"
+ /// (optional)the ID of the milestone to link to the test case
+ /// (optional)a comma-separated list of references/requirements
+ /// (optional)
+ /// result of the command
+ protected CommandResult _UpdateCase_(ulong caseID, string title, ulong? typeID = null, ulong? priorityID = null, string estimate = null, ulong? milestoneID = null, string refs = null, JObject customs = null)
+ {
+ if (string.IsNullOrWhiteSpace(title))
+ {
+ return new CommandResult(false, 0, new ArgumentNullException("title"));
+ }
+ string uri = _CreateUri_(_CommandType_.update, _NODE_CASE_, caseID);
+ Case tmpCase = new Case() { Title = title, TypeID = typeID, PriorityID = priorityID, Estimate = estimate, MilestoneID = milestoneID, References = refs };
+ JObject jsonParams = JsonUtility.Merge(tmpCase.GetJson(), customs);
+ return _SendCommand(uri, jsonParams);
+ }
+
+ ///
+ /// Command type's available
+ ///
+ protected enum _CommandType_
+ {
+ get,
+ add,
+ update,
+ delete,
+ close
+ }
+
+ #endregion Protected Methods
+
+ #region Private Methods
+ /// makes an http get call to the testrail
+ /// uri of the endpoint
+ /// result of the call
+ private CommandResult _CallTestRailGetEndpoint(string uri)
+ {
+ uri = _URL_ + uri;
+ OnHTTPRequestSent(this, new HTTPRequestSentEventArgs("GET", new Uri(uri)));
+ CommandResult cr;
+ try
+ {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+ request.AllowAutoRedirect = true;
+ string authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(_UserName_ + ":" + _Password_));
+ request.Headers["Authorization"] = "Basic " + authInfo;
+ request.UserAgent = "TestRail Client for .NET";
+ request.Method = "GET";
+ request.Accept = "application/json";
+ request.ContentType = "application/json";
+
+ // receive the response
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ Stream responseDataStream = response.GetResponseStream();
+ StreamReader reader = new StreamReader(responseDataStream);
+ string responseFromServer = reader.ReadToEnd();
+ reader.Close();
+ response.Close();
+ cr = new CommandResult(response.StatusCode == HttpStatusCode.OK, responseFromServer);
+ }
+ catch (Exception e) { cr = new CommandResult(false, e.ToString()); }
+ if (!cr.WasSuccessful)
+ {
+ OnOperationFailed(this, "HTTP RESPONSE: " + cr.Value);
+ }
+ else
+ {
+ OnHTTPResponseReceived(this, cr.Value);
+ }
+ return cr;
+ }
+
+ /// makes an http post call to the testrail
+ /// uri of the endpoint
+ /// post parameters alternating between keys and values
+ /// result of the call
+ private CommandResult _CallPostEndpoint(string uri, JObject json = null)
+ {
+ uri = _URL_ + uri;
+ string postContent = null;
+ if (null != json)
+ {
+ postContent = json.ToString();
+ }
+ OnHTTPRequestSent(this, new HTTPRequestSentEventArgs("POST", new Uri(uri), postContent));
+
+ CommandResult cr;
+ try
+ {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+ request.AllowAutoRedirect = true;
+ string authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(_UserName_ + ":" + _Password_));
+ request.Headers["Authorization"] = "Basic " + authInfo;
+ request.UserAgent = "TestRail Client for .NET";
+ request.Method = "POST";
+ request.ContentType = "application/json";
+ request.Accept = "application/json";
+
+ // add post data to the request
+ if (!string.IsNullOrWhiteSpace(postContent))
+ {
+ byte[] byteArray = Encoding.UTF8.GetBytes(postContent);
+ request.ContentLength = byteArray.Length;
+ Stream requestDataStream = request.GetRequestStream();
+ requestDataStream.Write(byteArray, 0, byteArray.Length);
+ requestDataStream.Close();
+ }
+
+ // receive the response
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ Stream responseDataStream = response.GetResponseStream();
+ StreamReader reader = new StreamReader(responseDataStream);
+ string responseFromServer = reader.ReadToEnd();
+ reader.Close();
+ response.Close();
+ cr = new CommandResult(response.StatusCode == HttpStatusCode.OK, responseFromServer);
+ }
+ catch (Exception e) { cr = new CommandResult(false, e.ToString()); }
+ if (!cr.WasSuccessful)
+ {
+ OnOperationFailed(this, "HTTP RESPONSE: " + cr.Value);
+ }
+ else
+ {
+ OnHTTPResponseReceived(this, cr.Value);
+ }
+ return cr;
+ }
+
+ /// Send a command to the server
+ /// uri to send
+ /// parameter
+ ///
+ private CommandResult _SendCommand(string uri, JObject jsonParams = null)
+ {
+ Exception exe = null;
+ ulong resultValue = 0;
+ bool wasSuccessful = false;
+
+ try
+ {
+ CommandResult result = _CallPostEndpoint(uri, jsonParams);
+ wasSuccessful = result.WasSuccessful;
+ if (wasSuccessful)
+ {
+ if (!string.IsNullOrWhiteSpace(result.Value))
+ {
+ JObject json = JObject.Parse(result.Value);
+ JToken token = json["id"];
+
+ try
+ {
+ if (null == token)
+ {
+ // do nothing
+ }
+ else if (JTokenType.String == token.Type)
+ {
+ // for plan entry
+ resultValue = (ulong)(json["runs"][0]["id"]);
+ }
+ else if (JTokenType.Integer == token.Type)
+ {
+ resultValue = (ulong)json["id"];
+ }
+ }
+ catch
+ {
+ // do nothing since result value is already 0
+ }
+ }
+ }
+ else
+ {
+ exe = new Exception(result.Value);
+ }
+ }
+ catch (Exception e)
+ {
+ exe = e;
+ }
+
+ return new CommandResult(wasSuccessful, resultValue, exe);
+ }
+
+ ///
+ /// Determines if at least one of the case ids given is contained in the project and suite
+ ///
+ /// id of the project
+ /// id of the suite
+ ///
+ ///
+ private bool _CasesFoundInSuite(ulong projectID, ulong suiteID, HashSet caseIDs)
+ {
+ bool atLeastOneCaseFoundInSuite = false;
+ List validCases = GetCases(projectID, suiteID);
+ foreach (Case tmpCase in validCases)
+ {
+ if (tmpCase.ID.HasValue && caseIDs.Contains(tmpCase.ID.Value))
+ {
+ atLeastOneCaseFoundInSuite = true;
+ break;
+ }
+ }
+ return atLeastOneCaseFoundInSuite;
+ }
+
+ ///
+ /// Create a priority dictionary
+ ///
+ /// dictionary of priority ID (from test rail) to priority levels(where Higher value means higher priority)
+ private Dictionary _CreatePrioritiesDict()
+ {
+ Dictionary tmpDict = new Dictionary();
+ List priorityList = GetPriorities();
+ foreach (Priority priority in priorityList)
+ {
+ if (null != priority)
+ {
+ tmpDict[priority.ID] = priority.PriorityLevel;
+ }
+ }
+ return tmpDict;
+ }
+ #endregion Private Methods
+ }
+}
diff --git a/TestRail/Types/Case.cs b/TestRail/Types/Case.cs
new file mode 100644
index 0000000..73b32c2
--- /dev/null
+++ b/TestRail/Types/Case.cs
@@ -0,0 +1,98 @@
+using Newtonsoft.Json.Linq;
+using System;
+
+namespace TestRail.Types
+{
+ /// stores information about a case
+ public class Case
+ {
+ #region Public Properties
+ /// id of the case
+ public ulong? ID { get; set; }
+
+ /// title of the case
+ public string Title { get; set; }
+
+ /// section id of the case
+ public ulong? SectionID { get; set; }
+
+ /// type id of the case
+ public ulong? TypeID { get; set; }
+
+ /// priority id of the case
+ public ulong? PriorityID { get; set; }
+
+ /// references for the case
+ public string References { get; set; }
+
+ /// the milestone this case was associated with
+ public ulong? MilestoneID { get; set; }
+
+ /// the user who created this case
+ public ulong? CreatedBy { get; set; }
+
+ /// creation date
+ public DateTime? CreatedOn { get; set; }
+
+ /// estimate time this case will take
+ public string Estimate { get; set; }
+
+ /// estimate forecast
+ public string EstimateForecast { get; set; }
+
+ /// suite id for this case
+ public ulong? SuiteID { get; set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Title;
+ }
+
+ /// parses json into a case
+ /// json to parse
+ /// case corresponding to the json
+ public static Case Parse(JObject json)
+ {
+ Case c = new Case();
+ c.ID = (ulong?)json["id"];
+ c.Title = (string)json["title"];
+ c.SectionID = (ulong?)json["section_id"];
+ c.TypeID = (ulong?)json["type_id"];
+ c.PriorityID = (ulong?)json["priority_id"];
+ c.References = (string)json["refs"];
+ c.MilestoneID = (ulong?)json["milestone_id"];
+ c.CreatedBy = (ulong)json["created_by"];
+ c.CreatedOn = (null == (int?)json["created_on"]) ? (DateTime?)null : new DateTime(1970, 1, 1).AddSeconds((int)json["created_on"]);
+ c.Estimate = (string)json["estimate"];
+ c.EstimateForecast = (string)json["estimate_forecast"];
+ c.SuiteID = (ulong)json["suite_id"];
+
+ return c;
+ }
+
+ /// creates a json object with the given parameters
+ /// title of the case
+ /// (optional)the ID of the case type
+ /// (optional)the id of the case priority
+ /// (optional)the estimate, e.g. "30s" or "1m 45s"
+ /// (optional)the ID of the milestone to link to the test case
+ /// (optional)a comma-separated list of references/requirements
+ /// json object for case
+ public virtual JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (!string.IsNullOrWhiteSpace(Title)) { jsonParams.title = Title; }
+ if (null != TypeID) { jsonParams.type_id = TypeID.Value; }
+ if (null != PriorityID) { jsonParams.priority_id = PriorityID.Value; }
+ if (!string.IsNullOrWhiteSpace(Estimate)) { jsonParams.estimate = Estimate; }
+ if (null != MilestoneID) { jsonParams.milestone_id = MilestoneID.Value; }
+ if (!string.IsNullOrWhiteSpace(References)) { jsonParams.refs = References; }
+ return jsonParams;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/CaseField.cs b/TestRail/Types/CaseField.cs
new file mode 100644
index 0000000..c21b5b4
--- /dev/null
+++ b/TestRail/Types/CaseField.cs
@@ -0,0 +1,78 @@
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+
+namespace TestRail.Types
+{
+ /// stores information about a case field
+ public class CaseField
+ {
+ #region Public Properties
+ /// id of the field
+ public ulong? ID { get; private set; }
+
+ /// easy name of the custom case field
+ public string Name { get; private set; }
+
+ /// system name of the custom case field
+ public string SystemName { get; private set; }
+
+ /// entity id
+ public ulong? EntityID { get; private set; }
+
+ /// display label for the custom case field
+ public string Label { get; private set; }
+
+ /// description of the custom case field
+ public string Description { get; private set; }
+
+ /// type of custom case field as described by the case type
+ public ulong? TypeID { get; private set; }
+
+ /// location id
+ public ulong? LocationID { get; private set; }
+
+ /// display order
+ public ulong? DisplayOrder { get; private set; }
+
+ /// list of configurations for this case field
+ public List Configs { get; private set; }
+
+ /// is multi
+ public bool? IsMulti { get; private set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// Parses the json object into a CaseField object
+ /// json to parse into a CaseField
+ /// CaseField corresponding to the json
+ public static CaseField Parse(JObject json)
+ {
+ CaseField cf = new CaseField();
+ cf.ID = (ulong?)json["id"];
+ cf.Name = (string)json["name"];
+ cf.SystemName = (string)json["system_name"];
+ cf.EntityID = (ulong?)json["entity_id"];
+ cf.Label = (string)json["label"];
+ cf.Description = (string)json["description"];
+ cf.TypeID = (ulong?)json["type_id"];
+ cf.LocationID = (ulong?)json["location_id"];
+ cf.DisplayOrder = (ulong?)json["display_order"];
+ JArray jarray = json["configs"] as JArray;
+ if (null != jarray)
+ {
+ cf.Configs = JsonUtility.ConvertJArrayToList(jarray, Config.Parse);
+ }
+ cf.IsMulti = (bool?)json["is_multi"];
+ return cf;
+ }
+
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/CaseType.cs b/TestRail/Types/CaseType.cs
new file mode 100644
index 0000000..39d2bc5
--- /dev/null
+++ b/TestRail/Types/CaseType.cs
@@ -0,0 +1,40 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a case type
+ public class CaseType
+ {
+ #region Public Properties
+ /// ID of the case type
+ public ulong? ID { get; protected set; }
+
+ /// Name of the case type
+ public string Name { get; protected set; }
+
+ /// is the case type the default
+ public bool? IsDefault { get; protected set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a suite
+ /// json to parse
+ /// suite corresponding to the json
+ public static CaseType Parse(JObject json)
+ {
+ CaseType ct = new CaseType();
+ ct.ID = (ulong?)json["id"];
+ ct.Name = (string)json["name"];
+ ct.IsDefault = (bool?)json["is_default"];
+ return ct;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Config.cs b/TestRail/Types/Config.cs
new file mode 100644
index 0000000..c8e312f
--- /dev/null
+++ b/TestRail/Types/Config.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a config
+ public class Config
+ {
+ #region Public Properties
+ /// Options for this configuration
+ public ConfigOption Option { get; private set; }
+
+ /// Guid unique identifier as a string
+ public string ID { get; private set; }
+
+ /// Configuration context
+ public ConfigContext Context { get; private set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// Constructor
+ /// json object to parse into a Config
+ public static Config Parse(JObject json)
+ {
+ Config c = new Config();
+ c.Option = ConfigOption.Parse((JObject)json["options"]);
+ c.Context = ConfigContext.Parse((JObject)json["context"]);
+ c.ID = (string)json["id"];
+ return c;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/ConfigContext.cs b/TestRail/Types/ConfigContext.cs
new file mode 100644
index 0000000..34c32c4
--- /dev/null
+++ b/TestRail/Types/ConfigContext.cs
@@ -0,0 +1,41 @@
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+
+namespace TestRail.Types
+{
+ /// stores informations about the context for a case field's config section
+ public class ConfigContext
+ {
+ #region Public Properties
+ /// Is the context global
+ public bool? IsGlobal { get; private set; }
+
+ /// List of project IDs
+ public List ProjectIDs { get; private set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// parse a json object into a Config Context
+ /// takes a json object and converts it to a ConfigContext
+ public static ConfigContext Parse(JObject json)
+ {
+ ConfigContext cc = new ConfigContext();
+ cc.IsGlobal = (bool?)json["is_global"];
+
+ // check to see if the project ids is empty
+ JToken jval = json["project_ids"];
+ if (null != jval && jval.HasValues)
+ {
+ // add values to the list if not empty
+ cc.ProjectIDs = new List();
+ JArray jarray = (JArray)jval;
+ foreach (JValue jsonItem in jarray)
+ {
+ cc.ProjectIDs.Add((string)jsonItem);
+ }
+ }
+ return cc;
+ }
+ #endregion
+ }
+}
diff --git a/TestRail/Types/ConfigOption.cs b/TestRail/Types/ConfigOption.cs
new file mode 100644
index 0000000..6e847e5
--- /dev/null
+++ b/TestRail/Types/ConfigOption.cs
@@ -0,0 +1,36 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about an option for a case field's configuration
+ public class ConfigOption
+ {
+ #region Public Properties
+ /// is this option required
+ public bool? IsRequired { get; private set; }
+
+ /// Default value for the option
+ public string DefaultValue { get; private set; }
+
+ /// format of the option
+ public string Format { get; private set; }
+
+ /// row
+ public string Rows { get; private set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// parse a json object into a Config Option
+ /// converts the json object to a ConfigOption
+ public static ConfigOption Parse(JObject json)
+ {
+ ConfigOption co = new ConfigOption();
+ co.IsRequired = (bool?)json["is_required"];
+ co.DefaultValue = (string)json["default_value"];
+ co.Format = (string)json["format"];
+ co.Rows = (string)json["rows"];
+ return co;
+ }
+ #endregion
+ }
+}
diff --git a/TestRail/Types/Milestone.cs b/TestRail/Types/Milestone.cs
new file mode 100644
index 0000000..3fe0db8
--- /dev/null
+++ b/TestRail/Types/Milestone.cs
@@ -0,0 +1,74 @@
+using Newtonsoft.Json.Linq;
+using System;
+
+namespace TestRail.Types
+{
+ /// stores information about a milestone
+ public class Milestone
+ {
+ #region Properties
+ /// id of the milestone
+ public ulong ID { get; private set; }
+
+ /// name of the milestone
+ public string Name { get; set; }
+
+ /// description of the milestone
+ public string Description { get; set; }
+
+ /// true if the milestone is completed
+ public bool? IsCompleted { get; set; }
+
+ /// date on which the milestone is due
+ public DateTime? DueOn { get; set; }
+
+ /// date on which the milestone was completed
+ public DateTime? CompletedOn { get; private set; }
+
+ /// id of the project with which the milestone is associated
+ public ulong ProjectID { get; private set; }
+
+ /// the url for to view the milestone
+ public string Url { get; private set; }
+ #endregion Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a milestone
+ /// json to parse
+ /// milestone corresponding to the json
+ public static Milestone Parse(JObject json)
+ {
+ Milestone m = new Milestone();
+ m.ID = (ulong)json["id"];
+ m.Name = (string)json["name"];
+ m.Description = (string)json["description"];
+ m.IsCompleted = (bool?)json["is_completed"];
+ m.DueOn = ((null == (int?)json["due_on"]) ? (DateTime?)null : new DateTime(1970, 1, 1).AddSeconds((int)json["due_on"]));
+ m.CompletedOn = ((null == (int?)json["completed_on"]) ? (DateTime?)null : new DateTime(1970, 1, 1).AddSeconds((int)json["completed_on"]));
+ m.ProjectID = (ulong)json["project_id"];
+ m.Url = (string)json["url"];
+ return m;
+ }
+
+ /// Creates a json object for this class
+ /// json object that represents this class
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ if (!string.IsNullOrWhiteSpace(Description)) { jsonParams.description = Description; }
+ if (null != DueOn) { jsonParams.dueOn = DueOn.Value.ToUnixTimestamp(); }
+ if (null != IsCompleted) { jsonParams.is_completed = IsCompleted; }
+ return jsonParams;
+ }
+ #endregion Public Methods
+
+ }
+}
diff --git a/TestRail/Types/Plan.cs b/TestRail/Types/Plan.cs
new file mode 100644
index 0000000..c944bc4
--- /dev/null
+++ b/TestRail/Types/Plan.cs
@@ -0,0 +1,147 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+
+namespace TestRail.Types
+{
+ /// stores information about a plan
+ public class Plan
+ {
+ #region Public Properties
+ /// id of the plan
+ public ulong ID { get; set; }
+
+ /// name of the plan
+ public string Name { get; set; }
+
+ /// description of the plan
+ public string Description { get; set; }
+
+ /// id of the milestone associated with the plan
+ public ulong? MilestoneID { get; set; }
+
+ /// true if the plan has been completed
+ public bool IsCompleted { get; set; }
+
+ /// date on which the plan was completed
+ public DateTime? CompletedOn { get; set; }
+
+ /// number of tests in the plan that passed
+ public uint PassedCount { get; set; }
+
+ /// number of tests in the plan that are blocked
+ public uint BlockedCount { get; set; }
+
+ /// number of tests in the plan that are untested
+ public uint UntestedCount { get; set; }
+
+ /// number of tests in the plan that need to be retested
+ public uint RetestCount { get; set; }
+
+ /// number of tests in the plan that failed
+ public uint FailedCount { get; set; }
+
+ /// id of the project associated with the plan
+ public ulong ProjectID { get; set; }
+
+ /// ID of the user the plan is assigned to
+ public ulong? AssignedToID { get; set; }
+
+ /// url to view the results of the plan
+ public string Url { get; set; }
+
+ /// Custom Status 1 Count
+ public ulong CustomStatus1Count { get; set; }
+
+ /// Custom Status 2 Count
+ public ulong CustomStatus2Count { get; set; }
+
+ /// Custom Status 3 Count>
+ public ulong CustomStatus3Count { get; set; }
+
+ /// Custom Status 4 Count
+ public ulong CustomStatus4Count { get; set; }
+
+ /// Custom Status 5 Count
+ public ulong CustomStatus5Count { get; set; }
+
+ /// Custom Status 6 Count
+ public ulong CustomStatus6Count { get; set; }
+
+ /// Custom Status 7 Count
+ public ulong CustomStatus7Count { get; set; }
+
+ public List Entries { get; set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a plan
+ /// json to parse
+ /// plan corresponding to the json
+ public static Plan Parse(JObject json)
+ {
+ Plan p = new Plan();
+ p.ID = (ulong)json["id"];
+ p.Name = (string)json["name"];
+ p.Description = (string)json["description"];
+ p.MilestoneID = (ulong?)json["milestone_id"];
+ p.IsCompleted = (bool)json["is_completed"];
+ p.CompletedOn = (null == (int?)json["completed_on"]) ? (DateTime?)null : new DateTime(1970, 1, 1).AddSeconds((int)json["completed_on"]);
+ p.PassedCount = (uint)json["passed_count"];
+ p.BlockedCount = (uint)json["blocked_count"];
+ p.UntestedCount = (uint)json["untested_count"];
+ p.RetestCount = (uint)json["retest_count"];
+ p.FailedCount = (uint)json["failed_count"];
+ p.ProjectID = (ulong)json["project_id"];
+ p.AssignedToID = (ulong?)json["assignedto_id"];
+ p.Url = (string)json["url"];
+ p.CustomStatus1Count = (ulong)json["custom_status1_count"];
+ p.CustomStatus2Count = (ulong)json["custom_status2_count"];
+ p.CustomStatus3Count = (ulong)json["custom_status3_count"];
+ p.CustomStatus4Count = (ulong)json["custom_status4_count"];
+ p.CustomStatus5Count = (ulong)json["custom_status5_count"];
+ p.CustomStatus6Count = (ulong)json["custom_status6_count"];
+ p.CustomStatus7Count = (ulong)json["custom_status7_count"];
+
+ JArray jarray = json["entries"] as JArray;
+ if (null != jarray)
+ {
+ p.Entries = JsonUtility.ConvertJArrayToList(jarray, PlanEntry.Parse);
+ }
+
+ return p;
+ }
+
+ /// Get the json object that describes this object
+ /// the json object
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ if (!string.IsNullOrWhiteSpace(Description)) { jsonParams.description = Description; }
+ if (null != MilestoneID) { jsonParams.milestone_id = MilestoneID; }
+ if (null != Entries && 0 < Entries.Count)
+ {
+ JArray jarray = new JArray();
+ foreach (PlanEntry pe in Entries)
+ {
+ if (null != pe)
+ {
+ jarray.Add(pe.GetJson());
+ }
+ }
+ jsonParams.entries = jarray;
+ }
+
+ return jsonParams;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/PlanEntry.cs b/TestRail/Types/PlanEntry.cs
new file mode 100644
index 0000000..d097399
--- /dev/null
+++ b/TestRail/Types/PlanEntry.cs
@@ -0,0 +1,118 @@
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+
+namespace TestRail.Types
+{
+ /// stores information about a plan entry
+ public class PlanEntry
+ {
+ #region Public Properties
+ /// Guid of the plan entry
+ public string ID { get; set; }
+
+ public List RunIDList { get; private set; }
+
+ /// the id of the test suite for the test run
+ public ulong? SuiteID { get; set; }
+
+ /// name of the test run
+ public string Name { get; set; }
+
+ /// the ID of the user the test run should be assigned to
+ public ulong? AssignedToID { get; set; }
+
+ /// true for including all test cases of the test suite, false for a custom case selection
+ public bool? IncludeAll { get; private set; }
+
+ /// an array of case IDs for the custom case selection
+ public List CaseIDs { get; set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// Returns a json Object that represents this class
+ /// Json object that corresponds to this class
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (null != SuiteID) { jsonParams.suite_id = SuiteID; }
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ if (null != AssignedToID) { jsonParams.assignedto_id = AssignedToID.Value; }
+
+ if (null != CaseIDs && 0 < CaseIDs.Count)
+ {
+ JArray jarray = new JArray();
+ foreach (ulong caseID in CaseIDs)
+ {
+ jarray.Add(caseID);
+ }
+
+ jsonParams.include_all = false;
+ jsonParams.case_ids = jarray;
+ }
+ else
+ {
+ jsonParams.include_all = true;
+ }
+ return jsonParams;
+ }
+
+ /// Parse a json object to a PlanEntry
+ /// json object to parse
+ /// PlanEntry corresponding to a json object
+ public static PlanEntry Parse(JObject json)
+ {
+ PlanEntry pe = new PlanEntry();
+ pe.ID = (string)json["id"];
+ pe.SuiteID = (ulong?)json["suite_id"];
+ pe.Name = (string)json["name"];
+ pe.AssignedToID = (ulong?)json["assignedto_id"];
+ pe.IncludeAll = (bool?)json["include_all"];
+
+ pe.RunIDList = _ConvertToRunIDs(json["runs"] as JArray);
+ pe.CaseIDs = _ConvertToCaseIDs(json["case_ids"] as JArray);
+ return pe;
+ }
+ #endregion Public Methods
+
+ #region Private Methods
+ ///
+ /// Convert the JArray to a list of Run IDs
+ ///
+ /// json to parse
+ /// a list of run IDs, list of size 0 if none exist
+ private static List _ConvertToRunIDs(JArray jarray)
+ {
+ List list = new List();
+ if (null != jarray)
+ {
+ foreach (JToken jt in jarray)
+ {
+ if (null != (ulong?)jt["id"])
+ {
+ list.Add((ulong)jt["id"]);
+ }
+ }
+ }
+ return list;
+ }
+
+ ///
+ /// Convert the Jarray to a list of case IDs
+ ///
+ /// json to parse
+ /// a list of case IDs, list of size 0 if none exist
+ private static List _ConvertToCaseIDs(JArray jarray)
+ {
+ List list = new List();
+ if (null != jarray)
+ {
+ foreach (JValue jsonItem in jarray)
+ {
+ list.Add((ulong)jsonItem);
+ }
+ }
+ return list;
+ }
+ #endregion Private Methods
+ }
+}
diff --git a/TestRail/Types/Priority.cs b/TestRail/Types/Priority.cs
new file mode 100644
index 0000000..f6d0f40
--- /dev/null
+++ b/TestRail/Types/Priority.cs
@@ -0,0 +1,48 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a priority
+ public class Priority
+ {
+ #region Public Properties
+ /// id of the priority
+ public ulong ID { get; private set; }
+
+ /// name of the priority
+ public string Name { get; private set; }
+
+ /// a shortened name of the priority
+ public string ShortName { get; private set; }
+
+ /// true if the priority is default
+ public bool IsDefault { get; private set; }
+
+ /// Priority level
+ public int PriorityLevel { get; private set; }
+ #endregion Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a plan
+ /// json to parse
+ /// plan corresponding to the json
+ public static Priority Parse(JObject json)
+ {
+ Priority p = new Priority();
+ p.ID = (ulong)json["id"];
+ p.Name = (string)json["name"];
+ p.ShortName = (string)json["short_name"];
+ p.IsDefault = (bool)json["is_default"];
+ p.PriorityLevel = (int)json["priority"];
+ return p;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Project.cs b/TestRail/Types/Project.cs
new file mode 100644
index 0000000..43f55e9
--- /dev/null
+++ b/TestRail/Types/Project.cs
@@ -0,0 +1,73 @@
+using Newtonsoft.Json.Linq;
+using System;
+
+namespace TestRail.Types
+{
+ /// stores information about a project
+ public class Project
+ {
+ #region Public Properties
+ /// id of the project
+ public ulong ID { get; private set; }
+
+ /// name of the project
+ public string Name { get; set; }
+
+ /// url of the project
+ public string Url { get; private set; }
+
+ /// announcement associated with the project
+ public string Announcement { get; set; }
+
+ ///
+ /// true if the announcement should be displayed on the project's overview page and
+ /// false otherwise
+ ///
+ public bool? ShowAnnouncement { get; set; }
+
+ /// true if the project has been completed
+ public bool? IsCompleted { get; set; }
+
+ /// date on which the milestone was completed
+ public DateTime? CompletedOn { get; private set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a project
+ /// json to parse
+ /// project corresponding to the json
+ public static Project Parse(JObject json)
+ {
+ Project p = new Project();
+ p.ID = (ulong)json["id"];
+ p.Name = (string)json["name"];
+ p.Announcement = (string)json["announcement"];
+ p.ShowAnnouncement = (bool?)json["show_announcement"];
+ p.IsCompleted = (bool?)json["is_completed"];
+ p.Url = (string)json["url"];
+ p.CompletedOn = (null == (int?)json["completed_on"]) ? (DateTime?)null : new DateTime(1970, 1, 1).AddSeconds((int)json["completed_on"]);
+ return p;
+ }
+
+ /// Creates a json object for this class
+ /// json object that represents this class
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ if (!string.IsNullOrWhiteSpace(Announcement)) { jsonParams.announcement = Announcement; }
+ if (null != ShowAnnouncement) { jsonParams.show_announcement = ShowAnnouncement.Value; }
+ if (null != IsCompleted) { jsonParams.is_completed = IsCompleted.Value; }
+ return jsonParams;
+ }
+
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Result.cs b/TestRail/Types/Result.cs
new file mode 100644
index 0000000..62c7ffc
--- /dev/null
+++ b/TestRail/Types/Result.cs
@@ -0,0 +1,84 @@
+using Newtonsoft.Json.Linq;
+using System;
+
+namespace TestRail.Types
+{
+ /// stores information about the result of a test
+ public class Result
+ {
+ #region Public Properties
+ /// ID of the result
+ public ulong ID { get; set; }
+
+ /// ID of the test
+ public ulong TestID { get; set; }
+
+ /// ID of the test status
+ public ulong? StatusID { get; set; }
+
+ /// created by
+ public ulong? CreatedBy { get; set; }
+
+ /// result created on
+ public DateTime? CreatedOn { get; set; }
+
+ /// the ID of the user the test should be assigned to
+ public ulong? AssignedToID { get; set; }
+
+ /// the comment /description for the test result
+ public string Comment { get; set; }
+
+ /// the version or build tested against
+ public string Version { get; set; }
+
+ /// the time it took to execute the test
+ public TimeSpan? Elapsed { get; set; }
+
+ /// a comma-separated list of defects to link to the test result
+ public string Defects { get; set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ ///
+ /// string representation of the object
+ ///
+ /// string representation of the object
+ public override string ToString()
+ {
+ return string.Format("{0}:{1}", ID, Comment);
+ }
+ /// Parse the JSON into a Result
+ /// json object to parse
+ /// a Result
+ public static Result Parse(JObject json)
+ {
+ Result r = new Result();
+ r.ID = (ulong)json["id"];
+ r.TestID = (ulong)json["test_id"];
+ r.StatusID = (ulong?)json["status_id"];
+ r.CreatedBy = (ulong?)json["created_by"];
+ r.CreatedOn = (null != (int?)json["created_on"]) ? new DateTime(1970, 1, 1).AddSeconds((int)json["created_on"]) : (DateTime?)null;
+ r.AssignedToID = (ulong?)json["assignedto_id"];
+ r.Comment = (string)json["comment"];
+ r.Version = (string)json["version"];
+ r.Elapsed = (null != (long?)json["elapsed"]) ? new TimeSpan((long)json["elapsed"]) : (TimeSpan?)null;
+ r.Defects = (string)json["defects"];
+ return r;
+ }
+
+ /// Returns a json object that represents this class
+ /// json object that represents this class
+ public virtual JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (null != StatusID) { jsonParams.status_id = (int)StatusID; }
+ if (null != Comment) { jsonParams.comment = Comment; }
+ if (null != Version) { jsonParams.version = Version; }
+ if (null != Elapsed) { jsonParams.elapsed = Elapsed.Value.Ticks; }
+ if (null != Defects) { jsonParams.defects = Defects; }
+ if (null != AssignedToID) { jsonParams.assignedto_id = AssignedToID.Value; }
+ return jsonParams;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/ResultStatus.cs b/TestRail/Types/ResultStatus.cs
new file mode 100644
index 0000000..c54e6f8
--- /dev/null
+++ b/TestRail/Types/ResultStatus.cs
@@ -0,0 +1,43 @@
+namespace TestRail.Types
+{
+ /// the enumeration represents the status of a test result
+ public enum ResultStatus
+ {
+ /// the test has not been run
+ Untested = 0,
+ /// the test passed
+ Passed = 1,
+ /// the test is blocked
+ Blocked = 2,
+ /// the test needs to be rerun
+ Retest = 4,
+ /// the test failed
+ Failed = 5,
+ /// custom status 1
+ CustomStatus1 = 6,
+ /// custom status 2
+ CustomStatus2 = 7,
+ /// custom status 3
+ CustomStatus3 = 8,
+ /// custom status 4
+ CustomStatus4 = 9,
+ /// custom status 5
+ CustomStatus5 = 10,
+ /// custom status 6
+ CustomStatus6 = 11,
+ /// custom status 7
+ CustomStatus7 = 12,
+ }
+
+ /// extension methods for the status enum
+ public static class ResultStatusExtensions
+ {
+ /// gets the value of the enum as a string
+ /// the status
+ /// the value of the status enum as a string
+ public static string ValueAsString(this ResultStatus s)
+ {
+ return ((int)s).ToString();
+ }
+ }
+}
diff --git a/TestRail/Types/Run.cs b/TestRail/Types/Run.cs
new file mode 100644
index 0000000..5ad24d9
--- /dev/null
+++ b/TestRail/Types/Run.cs
@@ -0,0 +1,157 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+
+namespace TestRail.Types
+{
+ /// stores information about a run
+ public class Run
+ {
+ #region Properties
+ /// id of the run
+ public ulong? ID { get; private set; }
+
+ /// name of the run
+ public string Name { get; set; }
+
+ /// description of the run
+ public string Description { get; set; }
+
+ /// id of the suite associated with the run
+ public ulong? SuiteID { get; set; }
+
+ /// id of the milestone associated with the run
+ public ulong? MilestoneID { get; set; }
+
+ /// config for the run
+ public string Config { get; private set; }
+
+ /// true if the run has been completes
+ public bool? IsCompleted { get; private set; }
+
+ /// date on which the run which was completed
+ public DateTime? CompletedOn { get; private set; }
+
+ /// number of tests in the plan that passed
+ public uint? PassedCount { get; private set; }
+
+ /// number of tests in the plan that are blocked
+ public uint? BlockedCount { get; private set; }
+
+ /// number of tests in the plan that are untested
+ public uint? UntestedCount { get; private set; }
+
+ /// number of tests in the plan that need to be retested
+ public uint? RetestCount { get; private set; }
+
+ /// number of tests in the plan that failed
+ public uint? FailedCount { get; private set; }
+
+ /// id of the project associated with the run
+ public ulong? ProjectID { get; private set; }
+
+ /// id of the plan associated with the run
+ public ulong? PlanID { get; private set; }
+
+ /// is of the user it is assigned to
+ public ulong? AssignedTo { get; set; }
+
+ ///
+ public bool IncludeAll { get; set; }
+
+ ///
+ public ulong CustomStatus1Count { get; private set; }
+
+ ///
+ public ulong CustomStatus2Count { get; private set; }
+
+ ///
+ public ulong CustomStatus3Count { get; private set; }
+
+ ///
+ public ulong CustomStatus4Count { get; private set; }
+
+ ///
+ public ulong CustomStatus5Count { get; private set; }
+
+ ///
+ public ulong CustomStatus6Count { get; private set; }
+
+ ///
+ public ulong CustomStatus7Count { get; private set; }
+
+ ///
+ public string Url { get; private set; }
+
+ /// an array of case IDs for the custom case selection
+ public HashSet CaseIDs { get; set;}
+ #endregion Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a run
+ /// json to parse
+ /// run corresponding to the json
+ public static Run Parse(JObject json)
+ {
+ Run r = new Run();
+ r.ID = (ulong?)json["id"];
+ r.Name = (string)json["name"];
+ r.Description = (string)json["description"];
+ r.SuiteID = (ulong?)json["suite_id"];
+ r.MilestoneID = (ulong?)json["milestone_id"];
+ r.Config = (string)json["config"];
+ r.IsCompleted = (bool?)json["is_completed"];
+ r.CompletedOn = ((null == (int?)json["completed_on"]) ? (DateTime?)null : new DateTime(1970, 1, 1).AddSeconds((int)json["completed_on"]));
+ r.PassedCount = (uint?)json["passed_count"];
+ r.BlockedCount = (uint?)json["blocked_count"];
+ r.UntestedCount = (uint?)json["untested_count"];
+ r.RetestCount = (uint?)json["retest_count"];
+ r.FailedCount = (uint?)json["failed_count"];
+ r.ProjectID = (ulong?)json["project_id"];
+ r.PlanID = (ulong?)json["plan_id"];
+ r.CustomStatus1Count = (ulong)json["custom_status1_count"];
+ r.CustomStatus2Count = (ulong)json["custom_status2_count"];
+ r.CustomStatus3Count = (ulong)json["custom_status3_count"];
+ r.CustomStatus4Count = (ulong)json["custom_status4_count"];
+ r.CustomStatus5Count = (ulong)json["custom_status5_count"];
+ r.CustomStatus6Count = (ulong)json["custom_status6_count"];
+ r.CustomStatus7Count = (ulong)json["custom_status7_count"];
+ r.AssignedTo = (ulong?)json["assignedto_id"];
+ r.IncludeAll = (bool)json["include_all"];
+ r.Url = (string)json["url"];
+ return r;
+ }
+
+ /// Creates a json object for this class
+ /// json object that represents this class
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (null != SuiteID) { jsonParams.suite_id = SuiteID; }
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ if (null != Description) { jsonParams.description = Description; }
+ if (null != MilestoneID) { jsonParams.milestone_id = MilestoneID; }
+ if (null != AssignedTo) { jsonParams.assignedto_id = AssignedTo; }
+ jsonParams.include_all = IncludeAll;
+
+ if (null != CaseIDs && 0 < CaseIDs.Count)
+ {
+ JArray jarray = new JArray();
+ foreach (ulong caseID in CaseIDs)
+ {
+ jarray.Add(caseID);
+ }
+ jsonParams.case_ids = jarray;
+ }
+ return jsonParams;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Section.cs b/TestRail/Types/Section.cs
new file mode 100644
index 0000000..1fbd84d
--- /dev/null
+++ b/TestRail/Types/Section.cs
@@ -0,0 +1,66 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a section
+ public class Section
+ {
+ #region Public Properties
+
+ /// id of the section
+ public ulong? ID { get; set; }
+
+ /// name of the section
+ public string Name { get; set; }
+
+ /// id of the parent section of the section
+ public ulong? ParentID { get; set; }
+
+ /// depth of the section
+ public uint? Depth { get; set; }
+
+ /// display order of the section
+ public uint? DisplayOrder { get; set; }
+
+ /// id of the suite associated with the section
+ public ulong? SuiteID { get; set; }
+
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a section
+ /// json to parse
+ /// section corresponding to the json
+ public static Section Parse(JObject json)
+ {
+ Section s = new Section();
+ s.ID = (ulong?)json["id"];
+ s.Name = (string)json["name"];
+ s.ParentID = (ulong?)json["parent_id"];
+ s.Depth = (uint?)json["depth"];
+ s.DisplayOrder = (uint?)json["display_order"];
+ s.SuiteID = (ulong?)json["suite_id"];
+ return s;
+ }
+
+ /// Creates a json object for this class
+ /// json object that represents this class
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (null != SuiteID) { jsonParams.suite_id = SuiteID.Value; }
+ if (null != ParentID) { jsonParams.parent_id = ParentID.Value; }
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ return jsonParams;
+ }
+
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Status.cs b/TestRail/Types/Status.cs
new file mode 100644
index 0000000..c3936fd
--- /dev/null
+++ b/TestRail/Types/Status.cs
@@ -0,0 +1,65 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a status
+ public class Status
+ {
+ #region Public Properties
+ /// id of the status
+ public ulong ID { get; private set; }
+
+ ///
+ public string Name { get; private set; }
+
+ ///
+ public string label { get; private set; }
+
+ ///
+ public ulong ColorDark { get; private set; }
+
+ ///
+ public ulong ColorMedium { get; private set; }
+
+ ///
+ public ulong ColorBright { get; private set; }
+
+ ///
+ public bool IsSystem { get; private set; }
+
+ ///
+ public bool IsUntested { get; private set; }
+
+ ///
+ public bool IsFinal { get; private set; }
+
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// Parses json into a Status object
+ /// json to parse
+ /// Status object corresponding to the json
+ public static Status Parse(JObject json)
+ {
+ Status s = new Status();
+ s.ID = (ulong)json["id"];
+ s.Name = (string)json["name"];
+ s.label = (string)json["label"];
+ s.ColorDark = (ulong)json["color_dark"];
+ s.ColorMedium = (ulong)json["color_medium"];
+ s.ColorBright = (ulong)json["color_bright"];
+ s.IsSystem = (bool)json["is_system"];
+ s.IsUntested = (bool)json["is_untested"];
+ s.IsFinal = (bool)json["is_final"];
+ return s;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Step.cs b/TestRail/Types/Step.cs
new file mode 100644
index 0000000..fa09632
--- /dev/null
+++ b/TestRail/Types/Step.cs
@@ -0,0 +1,46 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a step
+ public class Step
+ {
+ #region Public Properties
+ /// description of the step
+ public string Description;
+ /// expected result for the step
+ public string Expected;
+ /// actual result for the step
+ public string Actual;
+ /// result of the step
+ public ResultStatus? Status;
+ #endregion
+
+ #region Public Methods
+ /// parses json into a step
+ /// json to parse
+ /// step corresponding to the json
+ public static Step Parse(JObject json)
+ {
+ Step s = new Step();
+ s.Description = (string)json["content"];
+ s.Expected = (string)json["expected"];
+ s.Actual = (string)json["actual"];
+ s.Status = (null != (int?)json["status_id"]) ? (ResultStatus)((int)json["status_id"]) : (ResultStatus?)null;
+ return s;
+ }
+
+ /// Get the json object that describes this class
+ /// json object for this class
+ public JObject GetJsonObject()
+ {
+ dynamic json = new JObject();
+ if (!string.IsNullOrWhiteSpace(Description)) { json.content = Description; }
+ if (!string.IsNullOrWhiteSpace(Expected)) { json.expected = Expected; }
+ if (!string.IsNullOrWhiteSpace(Actual)) { json.actual = Actual; }
+ if (null != Status) { json.status_id = (int)Status; }
+ return json;
+ }
+ #endregion
+ }
+}
diff --git a/TestRail/Types/Suite.cs b/TestRail/Types/Suite.cs
new file mode 100644
index 0000000..69c79a0
--- /dev/null
+++ b/TestRail/Types/Suite.cs
@@ -0,0 +1,58 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a suite
+ public class Suite
+ {
+ #region Public Properties
+ /// id of the suite
+ public ulong? ID { get; set; }
+
+ /// name of the suite
+ public string Name { get; set; }
+
+ /// description of the suite
+ public string Description { get; set; }
+
+ /// id of the project associated with the suite
+ public ulong? ProjectID { get; set; }
+
+ /// url to view the suite
+ public string Url { get; set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ /// parses json into a suite
+ /// json to parse
+ /// suite corresponding to the json
+ public static Suite Parse(JObject json)
+ {
+ Suite s = new Suite();
+ s.ID = (ulong?)json["id"];
+ s.Name = (string)json["name"];
+ s.Description = (string)json["description"];
+ s.ProjectID = (ulong?)json["project_id"];
+ s.Url = (string)json["url"];
+ return s;
+ }
+
+ /// Creates a json object for this class
+ /// json object that represents this class
+ public JObject GetJson()
+ {
+ dynamic jsonParams = new JObject();
+ if (!string.IsNullOrWhiteSpace(Name)) { jsonParams.name = Name; }
+ if (!string.IsNullOrWhiteSpace(Description)) { jsonParams.description = Description; }
+ return jsonParams;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/Types/Test.cs b/TestRail/Types/Test.cs
new file mode 100644
index 0000000..7d4c1fd
--- /dev/null
+++ b/TestRail/Types/Test.cs
@@ -0,0 +1,52 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a test
+ public class Test
+ {
+ #region Public Properties
+ /// id of the test
+ public ulong? ID { get; private set; }
+
+ /// id of the test case
+ public ulong? CaseID { get; set; }
+
+ /// id of the test run
+ public ulong? RunID { get; private set; }
+
+ /// test rail status id
+ public ResultStatus? Status { get; private set; }
+
+ /// id of the user the test is assigned to
+ public ulong? AssignedToID { get; private set; }
+
+ /// title of the test
+ public string Title;
+ #endregion
+
+ #region Public Methods
+ /// string representation of the object
+ /// string representation of the object
+ public override string ToString()
+ {
+ return Title;
+ }
+
+ /// parses a test from the supplied json
+ /// json for the test
+ /// test corresponding to the json
+ public static Test Parse(JObject json)
+ {
+ Test t = new Test();
+ t.ID = (ulong?)json["id"];
+ t.CaseID = (ulong?)json["case_id"];
+ t.RunID = (ulong?)json["run_id"];
+ t.Status = (ResultStatus?)((int)json["status_id"]);
+ t.AssignedToID = (ulong?)json["assignedto_id"];
+ t.Title = (string)json["title"];
+ return t;
+ }
+ #endregion
+ }
+}
diff --git a/TestRail/Types/User.cs b/TestRail/Types/User.cs
new file mode 100644
index 0000000..22f47fa
--- /dev/null
+++ b/TestRail/Types/User.cs
@@ -0,0 +1,68 @@
+using Newtonsoft.Json.Linq;
+
+namespace TestRail.Types
+{
+ /// stores information about a user
+ public class User
+ {
+ #region Public Properties
+ ///
+ /// id of the user
+ ///
+ public ulong ID { get; private set; }
+
+ ///
+ /// name of the user
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// email of the user
+ ///
+ public string Email { get; private set; }
+
+ ///
+ /// is the user an admin
+ ///
+ public bool IsAdmin { get; private set; }
+
+ ///
+ /// role id of the user
+ ///
+ public ulong? RoleID { get; private set; }
+
+ ///
+ /// Is the user active
+ ///
+ public bool IsActive { get; private set; }
+ #endregion Public Properties
+
+ #region Public Methods
+ ///
+ /// Displays the User's ID : User Name
+ ///
+ ///
+ public override string ToString()
+ {
+ return string.Format("{0}:{1}", ID, Name);
+ }
+
+ ///
+ /// Parses the json object and returns an User object
+ ///
+ /// json to parse
+ /// a user object corresponding to the json object
+ public static User Parse(JObject json)
+ {
+ User u = new User();
+ u.ID = (ulong)json["id"];
+ u.Name = (string)json["name"];
+ u.Email = (string)json["email"];
+ u.IsAdmin = json.Value("is_admin") ?? false;
+ u.RoleID = (ulong?)json["role_id"];
+ u.IsActive = (bool)json["is_active"];
+ return u;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/TestRail/packages.config b/TestRail/packages.config
new file mode 100644
index 0000000..6c8cafc
--- /dev/null
+++ b/TestRail/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file