コード例 #1
0
        //private int _userNum;

        public cntrlOptions_SpiraTeam()
        {
            InitializeComponent();

            ToolTip serverNotification = new ToolTip();

            serverNotification.Content = "URL of the server, not including 'Login.aspx' or 'ImportExport.asmx'. Examples:" + Environment.NewLine + "http://server/SpiraTeam" + Environment.NewLine + "https://localhost:10569/SpiraTeam";
            this.txbServer.ToolTip     = serverNotification;

            //Make the client.
            this._Client                    = new SpiraSoapService.SoapServiceClient();
            this.btnConnect.Tag             = false;
            this.cmbProjectList.ItemsSource = new List <SpiraProject>();

            //Populate from settings
            this.txbServer.Text = Properties.Settings.Default.SpiraUrl;
            this.txbUserID.Text = Properties.Settings.Default.SpiraLogin;
            if (String.IsNullOrEmpty(Properties.Settings.Default.SpiraPassword))
            {
                this.chkRememberPassword.IsChecked = false;
                this.txbUserPass.Password          = "";
            }
            else
            {
                this.chkRememberPassword.IsChecked = true;
                this.txbUserPass.Password          = Properties.Settings.Default.SpiraPassword;
            }
            this.chkDeleteItemsInSpira.IsChecked = Properties.Settings.Default.DeleteItemInSpiraIfMissing;
        }
コード例 #2
0
        /// <summary>
        /// Imports the test suites and sections
        /// </summary>
        /// <param name="streamWriter"></param>
        /// <param name="spiraClient"></param>
        /// <param name="testRailApi"></param>
        private void ImportTestSuites(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, TestLink testLinkApi, List <TestSuite> testSuites, int?parentTestFolderId = null)
        {
            //Get the test suites from the TestLink API
            if (testSuites != null)
            {
                foreach (TestSuite testSuite in testSuites)
                {
                    //Extract the user data
                    int testLinkId = testSuite.id;

                    //Create the new SpiraTest test folder
                    RemoteTestCaseFolder remoteTestFolder = new RemoteTestCaseFolder();
                    remoteTestFolder.Name = testSuite.name;
                    remoteTestFolder.ParentTestCaseFolderId = parentTestFolderId;
                    int newTestCaseFolderId = spiraClient.TestCase_CreateFolder(remoteTestFolder).TestCaseFolderId.Value;
                    streamWriter.WriteLine("Added test suite: " + testLinkId);

                    //Add to the mapping hashtables
                    if (!this.testSuiteMapping.ContainsKey(testLinkId))
                    {
                        this.testSuiteMapping.Add(testLinkId, newTestCaseFolderId);
                    }

                    //See if this test suite has any child test suites
                    List <TestSuite> childTestSuites = testLinkApi.GetTestSuitesForTestSuite(testLinkId);
                    if (childTestSuites != null && childTestSuites.Count > 0)
                    {
                        ImportTestSuites(streamWriter, spiraClient, testLinkApi, childTestSuites, newTestCaseFolderId);
                    }
                }
            }
        }
コード例 #3
0
        /// <summary>Hit when the users wants to get projects from the server or cancel an existing connection.</summary>
        /// <param name="sender">btnConnect</param>
        /// <param name="e">EventArgs</param>
        private void btnConnect_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;
            //They asked for it!
            if ((bool)btnConnect.Tag)
            {
                //Cancel the operation
                this._Client.Abort();
            }
            else
            {
                //Error check, first.
                if (this.CheckEntryFields())
                {
                    //Set display.
                    this.setDisplayforProgress(true);

                    //Save in settings
                    Properties.Settings.Default.SpiraUrl      = this.txbServer.Text.Trim();
                    Properties.Settings.Default.SpiraLogin    = this.txbUserID.Text.Trim();
                    Properties.Settings.Default.SpiraPassword = (chkRememberPassword.IsChecked.Value) ? this.txbUserPass.Password : "";

                    //Fire off a new client.
                    this._numRunning              = 0;
                    this.msgStatus.Text           = "Connecting to server...";
                    this._Client                  = new SpiraSoapService.SoapServiceClient();
                    this._Client.Endpoint.Address = new EndpointAddress(this.txbServer.Text + URI_APPEND);
                    BasicHttpBinding httpBinding = (BasicHttpBinding)this._Client.Endpoint.Binding;
                    WcfUtils.ConfigureBinding(httpBinding, this._Client.Endpoint.Address.Uri);
                    this._Client.Connection_Authenticate2Completed += new EventHandler <SpiraSoapService.Connection_Authenticate2CompletedEventArgs>(Client_Connection_Authenticate2Completed);
                    this._Client.Connection_Authenticate2Async(this.txbUserID.Text, this.txbUserPass.Password, Properties.Resources.Global_ApplicationName, this._numRunning++);
                }
            }
        }
コード例 #4
0
 /// <summary>Hit when the client finishes logging in.</summary>
 /// <param name="sender">Client</param>
 /// <param name="e">EventArgs</param>
 private void Client_Connection_Authenticate2Completed(object sender, SpiraSoapService.Connection_Authenticate2CompletedEventArgs e)
 {
     if (e.Error == null)
     {
         if (!e.Cancelled)
         {
             //Not canceled, no error.
             this.msgStatus.Text    = "Downloading project list...";
             this.msgStatus.ToolTip = null;
             SpiraSoapService.SoapServiceClient client = (SpiraSoapService.SoapServiceClient)sender;
             client.Project_RetrieveCompleted += new EventHandler <SpiraSoapService.Project_RetrieveCompletedEventArgs>(client_Project_RetrieveCompleted);
             client.Project_RetrieveAsync(this._numRunning++);
         }
         else
         {
             this.msgStatus.Text    = "Connection canceled.";
             this.msgStatus.ToolTip = null;
             this.setDisplayforProgress(false);
         }
     }
     else
     {
         this.msgStatus.Text    = "Error connecting to server.";
         this.msgStatus.ToolTip = e.Error.Message;
         this.setDisplayforProgress(false);
     }
 }
コード例 #5
0
        /// <summary>
        /// Imports the test runs and 'tests'
        /// </summary>
        /// <param name="streamWriter"></param>
        /// <param name="spiraClient"></param>
        /// <param name="testRailApi"></param>
        private void ImportTestRuns(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi)
        {
            //Get the test runs that are not part of a plan
            JArray testRuns = (JArray)testRailApi.SendGet("get_runs/" + this.testRailProjectId);

            if (testRuns != null)
            {
                foreach (JObject testRun in testRuns)
                {
                    ImportTestRun(streamWriter, spiraClient, testRailApi, testRun);
                }
            }

            //Next get the test runs for each plan
            foreach (KeyValuePair <int, int> kvp in this.testSetMapping)
            {
                //Get the test runs that are part of a plan
                int     testPlanId = kvp.Key;
                JObject testPlan   = (JObject)testRailApi.SendGet("get_plan/" + testPlanId);
                testRuns = (JArray)testPlan["entries"];
                if (testRuns != null)
                {
                    foreach (JObject testRun in testRuns)
                    {
                        ImportTestRun(streamWriter, spiraClient, testRailApi, testRun);
                    }
                }
            }
        }
コード例 #6
0
        private void ImportProject(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, TestLink testLinkApi)
        {
            try
            {
                streamWriter.WriteLine(String.Format("Importing Test Link project '{0}' to Spira...", this.testLinkProjectName));

                //Get the project info from the TestLink API
                TestProject tlProject = testLinkApi.GetProject(this.testLinkProjectName);
                if (tlProject == null)
                {
                    throw new ApplicationException(String.Format("Empty project data returned by TestLink for project '{0}' so aborting import", this.testLinkProjectId));
                }

                RemoteProject remoteProject = new RemoteProject();
                remoteProject.Name    = tlProject.name;
                remoteProject.Website = tlProject.notes;
                remoteProject.Active  = tlProject.active;

                //Reconnect and import the project
                spiraClient.Connection_Authenticate2(Properties.Settings.Default.SpiraUserName, Properties.Settings.Default.SpiraPassword, "TestLinkImporter");
                remoteProject = spiraClient.Project_Create(remoteProject, null);
                streamWriter.WriteLine("New Project '" + remoteProject.Name + "' Created");
                int projectId = remoteProject.ProjectId.Value;

                this.newProjectId = remoteProject.ProjectId.Value;
            }
            catch (Exception exception)
            {
                streamWriter.WriteLine("General error importing data from TestLink to Spira The error message is: " + exception.Message);
                throw;
            }
        }
コード例 #7
0
        /// <summary>
        /// Creates the WCF endpoints
        /// </summary>
        /// <param name="fullUri">The URI</param>
        /// <returns>The client class</returns>
        /// <remarks>We need to do this in code because the app.config file is not available in VSTO</remarks>
        public static SpiraSoapService.SoapServiceClient CreateClient(Uri fullUri)
        {
            //Configure the binding
            BasicHttpBinding httpBinding = new BasicHttpBinding();

            //Allow cookies and large messages
            httpBinding.AllowCookies           = true;
            httpBinding.MaxBufferSize          = 100000000;              //100MB
            httpBinding.MaxReceivedMessageSize = 100000000;              //100MB
            httpBinding.ReceiveTimeout         = new TimeSpan(0, 10, 0); //10 Minutes
            httpBinding.SendTimeout            = new TimeSpan(0, 10, 0); //10 Minutes
            httpBinding.ReaderQuotas.MaxStringContentLength = 2147483647;
            httpBinding.ReaderQuotas.MaxDepth              = 2147483647;
            httpBinding.ReaderQuotas.MaxBytesPerRead       = 2147483647;
            httpBinding.ReaderQuotas.MaxNameTableCharCount = 2147483647;
            httpBinding.ReaderQuotas.MaxArrayLength        = 2147483647;

            //Handle SSL if necessary
            if (fullUri.Scheme == "https")
            {
                httpBinding.Security.Mode = BasicHttpSecurityMode.Transport;
                httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

                //Allow self-signed certificates
                PermissiveCertificatePolicy.Enact("");
            }
            else
            {
                httpBinding.Security.Mode = BasicHttpSecurityMode.None;
            }

            //Create the new client with endpoint and HTTP Binding
            EndpointAddress endpointAddress = new EndpointAddress(fullUri.AbsoluteUri);

            SpiraSoapService.SoapServiceClient spiraSoapService = new SpiraSoapService.SoapServiceClient(httpBinding, endpointAddress);

            //Modify the operation behaviors to allow unlimited objects in the graph
            foreach (var operation in spiraSoapService.Endpoint.Contract.Operations)
            {
                var behavior = operation.Behaviors.Find <DataContractSerializerOperationBehavior>() as DataContractSerializerOperationBehavior;
                if (behavior != null)
                {
                    behavior.MaxItemsInObjectGraph = 2147483647;
                }
            }

            return(spiraSoapService);
        }
コード例 #8
0
        private void ImportUsers(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi, int userId)
        {
            //Get the users from the TestRail API
            JArray users = (JArray)testRailApi.SendGet("get_users");

            if (users != null)
            {
                foreach (JObject user in users)
                {
                    //Extract the user data
                    int    testRailId = user["id"].Value <int>();
                    string userName   = user["email"].Value <string>();
                    string fullName   = user["name"].Value <string>();
                    string firstName  = userName;
                    string lastName   = userName;
                    if (fullName.Contains(' '))
                    {
                        firstName = fullName.Substring(0, fullName.IndexOf(' '));
                        lastName  = fullName.Substring(fullName.IndexOf(' ') + 1);
                    }
                    else
                    {
                        firstName = fullName;
                        lastName  = fullName;
                    }
                    string emailAddress = userName;
                    bool   isActive     = user["is_active"].Value <bool>();

                    //See if we're importing users or mapping to a single user
                    if (Properties.Settings.Default.Users)
                    {
                        //Default to observer role for all imports, for security reasons
                        RemoteUser remoteUser = new RemoteUser();
                        remoteUser.FirstName    = firstName;
                        remoteUser.LastName     = lastName;
                        remoteUser.UserName     = userName;
                        remoteUser.EmailAddress = emailAddress;
                        remoteUser.Active       = isActive;
                        remoteUser.Approved     = true;
                        remoteUser.Admin        = false;
                        userId = spiraClient.User_Create(remoteUser, Properties.Settings.Default.NewUserPassword, "What was my default email address?", emailAddress, PROJECT_ROLE_ID).UserId.Value;
                    }

                    //Add the mapping to the hashtable for use later on
                    this.usersMapping.Add(testRailId, userId);
                }
            }
        }
コード例 #9
0
        /// <summary>Creates the WCF endpoints</summary>
        /// <param name="fullUri">The URI</param>
        /// <returns>The client class</returns>
        /// <remarks>We need to do this in code because the app.config file is not available in VSTO</remarks>
        public static SpiraSoapService.SoapServiceClient CreateClient_Spira5(Uri baseUri)
        {
            Uri fullUri = new Uri(baseUri.ToString() + Common.SERVICE_SPIRA_5);

            BasicHttpBinding httpBinding = SpiraClientFactory.createBinding(fullUri.Scheme);

            //Create the new client with endpoint and HTTP Binding
            EndpointAddress endpointAddress = new EndpointAddress(fullUri.AbsoluteUri);

            SpiraSoapService.SoapServiceClient spiraSoapService = new SpiraSoapService.SoapServiceClient(httpBinding, endpointAddress);

            //Modify the operation behaviors to allow unlimited objects in the graph
            foreach (var operation in spiraSoapService.Endpoint.Contract.Operations)
            {
                var behavior = operation.Behaviors.Find <DataContractSerializerOperationBehavior>() as DataContractSerializerOperationBehavior;
                if (behavior != null)
                {
                    behavior.MaxItemsInObjectGraph = 2147483647;
                }
            }
            return(spiraSoapService);
        }
コード例 #10
0
        /// <summary>
        /// Imports the test plans
        /// </summary>
        /// <param name="streamWriter"></param>
        /// <param name="spiraClient"></param>
        /// <param name="testRailApi"></param>
        private void ImportTestPlans(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi)
        {
            //Get the test cases from the TestRail API
            JArray testPlans = (JArray)testRailApi.SendGet("get_plans/" + this.testRailProjectId);

            if (testPlans != null)
            {
                foreach (JObject testPlan in testPlans)
                {
                    //Extract the test rail data
                    int  testRailId  = testPlan["id"].Value <int>();
                    bool isCompleted = testPlan["is_completed"].Value <bool>();

                    //Create the new SpiraTest test set
                    RemoteTestSet remoteTestSet = new RemoteTestSet();
                    remoteTestSet.Name            = testPlan["name"].Value <string>();
                    remoteTestSet.Description     = testPlan["description"].Value <string>();
                    remoteTestSet.TestSetStatusId = (isCompleted) ? /* Completed */ 3 : /* Not Started */ 1;
                    remoteTestSet.TestRunTypeId   = 1; /* Manual */
                    int?createdById = testPlan["created_by"].Value <int?>();
                    if (createdById.HasValue && this.usersMapping.ContainsKey(createdById.Value))
                    {
                        remoteTestSet.CreatorId = this.usersMapping[createdById.Value];
                    }

                    int newTestSetId = spiraClient.TestSet_Create(remoteTestSet).TestSetId.Value;
                    streamWriter.WriteLine("Added test plan: " + testRailId);

                    //Add to the mapping dictionary
                    if (!this.testSetMapping.ContainsKey(testRailId))
                    {
                        this.testSetMapping.Add(testRailId, newTestSetId);
                    }

                    //Test Rail doesn't map test cases to test plans, so we don't map them
                }
            }
        }
コード例 #11
0
        private void ImportProject(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi)
        {
            try
            {
                streamWriter.WriteLine(String.Format("Importing Test Rail project {0} to Spira...", this.testRailProjectId));

                //Get the project info from the TestRail API
                JObject project = (JObject)testRailApi.SendGet("get_project/" + this.testRailProjectId);
                if (project == null)
                {
                    throw new ApplicationException(String.Format("Empty project data returned by TestRail for project '{0}' so aborting import", this.testRailProjectId));
                }

                RemoteProject remoteProject = new RemoteProject();
                remoteProject.Name    = project["name"].Value <string>();
                remoteProject.Website = project["url"].Value <string>();
                remoteProject.Active  = !project["is_completed"].Value <bool>();

                //Reconnect and import the project
                spiraClient.Connection_Authenticate2(Properties.Settings.Default.SpiraUserName, Properties.Settings.Default.SpiraPassword, "TestRailImporter");
                remoteProject = spiraClient.Project_Create(remoteProject, null);
                streamWriter.WriteLine("New Project '" + remoteProject.Name + "' Created");
                int projectId = remoteProject.ProjectId.Value;

                this.newProjectId = remoteProject.ProjectId.Value;
            }
            catch (APIException exception)
            {
                streamWriter.WriteLine("Unable to access the TestRail API. The error message is: " + exception.Message);
                throw;
            }
            catch (Exception exception)
            {
                streamWriter.WriteLine("General error importing data from TestRail to Spira The error message is: " + exception.Message);
                throw;
            }
        }
コード例 #12
0
        /// <summary>
        /// This method is responsible for actually importing the data
        /// </summary>
        /// <param name="stateInfo">State information handle</param>
        /// <remarks>This runs in background thread to avoid freezing the progress form</remarks>
        public void ImportData(object stateInfo)
        {
            //First open up the textfile that we will log information to (used for debugging purposes)
            string       debugFile    = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\Spira_TestLink_Import.log";
            StreamWriter streamWriter = File.CreateText(debugFile);

            try
            {
                streamWriter.WriteLine("Starting import at: " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());

                //Create the mapping hashtables
                this.testCaseMapping              = new Dictionary <int, int>();
                this.testSuiteMapping             = new Dictionary <int, int>();
                this.testSuiteSectionMapping      = new Dictionary <int, int>();
                this.testStepMapping              = new Dictionary <int, int>();
                this.usersMapping                 = new Dictionary <int, int>();
                this.testRunStepMapping           = new Dictionary <int, int>();
                this.testRunStepToTestStepMapping = new Dictionary <int, int>();
                this.testSetMapping               = new Dictionary <int, int>();
                this.testSetTestCaseMapping       = new Dictionary <int, int>();

                //Connect to Spira
                streamWriter.WriteLine("Connecting to Spira...");
                SpiraSoapService.SoapServiceClient spiraClient = new SpiraSoapService.SoapServiceClient();

                //Set the end-point and allow cookies
                string spiraUrl = Properties.Settings.Default.SpiraUrl + ImportForm.IMPORT_WEB_SERVICES_URL;
                spiraClient.Endpoint.Address = new EndpointAddress(spiraUrl);
                BasicHttpBinding httpBinding = (BasicHttpBinding)spiraClient.Endpoint.Binding;
                ConfigureBinding(httpBinding, spiraClient.Endpoint.Address.Uri);

                bool success = spiraClient.Connection_Authenticate2(Properties.Settings.Default.SpiraUserName, Properties.Settings.Default.SpiraPassword, "TestLinkImporter");
                if (!success)
                {
                    string message = "Failed to authenticate with Spira using login: '******' so terminating import!";
                    streamWriter.WriteLine(message);
                    streamWriter.Close();

                    //Display the exception message
                    this.progressForm.ProgressForm_OnError(message);
                    return;
                }

                //Connect to TestLink
                string   apiKey      = Properties.Settings.Default.TestLinkApiKey;
                string   testLinkUrl = Properties.Settings.Default.TestLinkUrl + Utils.TESTLINK_URL_SUFFIX_XML_RPC;
                TestLink testLink    = new TestLink(apiKey, testLinkUrl);

                //1) Create a new project
                ImportProject(streamWriter, spiraClient, testLink);

                //**** Show progress ****
                streamWriter.WriteLine("Project Created");
                this.progressForm.ProgressForm_OnProgressUpdate(1);

                //2) Import test suites
                //Get the test suites from the TestLink API
                List <TestSuite> testSuites = testLink.GetFirstLevelTestSuitesForTestProject(this.testLinkProjectId);
                ImportTestSuites(streamWriter, spiraClient, testLink, testSuites);

                //**** Show progress ****
                streamWriter.WriteLine("Imported Test Suites");
                this.progressForm.ProgressForm_OnProgressUpdate(2);

                //**** Show progress ****
                streamWriter.WriteLine("Imported Test Cases and Steps");
                this.progressForm.ProgressForm_OnProgressUpdate(3);

                //3) Import test cases and test steps
                ImportTestCasesAndSteps(streamWriter, spiraClient, testLink);

                //**** Show progress ****
                streamWriter.WriteLine("Completed Import");
                this.progressForm.ProgressForm_OnProgressUpdate(4);

                //**** Mark the form as finished ****
                streamWriter.WriteLine("Import completed at: " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());
                this.progressForm.ProgressForm_OnFinish();

                //Close the debugging file
                streamWriter.Close();
            }
            catch (Exception exception)
            {
                //Log the error
                streamWriter.WriteLine("*ERROR* Occurred during Import: '" + exception.Message + "' at " + exception.Source + " (" + exception.StackTrace + ")");
                streamWriter.Close();

                //Display the exception message
                this.progressForm.ProgressForm_OnError(exception);
            }
        }
コード例 #13
0
        /// <summary>
        /// Imports test cases and associated steps
        /// </summary>
        /// <param name="streamWriter"></param>
        /// <param name="spiraClient"></param>
        /// <param name="testRailApi"></param>
        private void ImportTestCases(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi)
        {
            //If we have any test suites, then we need to query by suite, otherwise we can get all for the project
            foreach (KeyValuePair <int, int> kvp in this.testSuiteMapping)
            {
                //Get the test cases from the TestRail API
                JArray testCases = (JArray)testRailApi.SendGet("get_cases/" + this.testRailProjectId + "&suite_id=" + kvp.Key);
                if (testCases != null)
                {
                    foreach (JObject testCase in testCases)
                    {
                        //Extract the user data
                        int testRailId = testCase["id"].Value <int>();

                        //Create the new SpiraTest test case
                        RemoteTestCase remoteTestCase = new RemoteTestCase();
                        remoteTestCase.Name = testCase["title"].Value <string>();
                        if (testCase["custom_description"] != null && testCase["custom_preconds"] != null)
                        {
                            string description = testCase["custom_description"].Value <string>();
                            string preconds    = testCase["custom_preconds"].Value <string>();
                            remoteTestCase.Description = description + " " + preconds;
                        }
                        remoteTestCase.TestCaseTypeId   = /* Functional */ 3;
                        remoteTestCase.TestCaseStatusId = /* Ready For Test */ 5;
                        if (testCase["priority_id"] != null)
                        {
                            int?testCasePriorityId = testCase["priority_id"].Value <int?>();
                            if (testCasePriorityId.HasValue && testCasePriorityId >= 1 && testCasePriorityId <= 4)
                            {
                                remoteTestCase.TestCasePriorityId = testCasePriorityId.Value;
                            }
                        }
                        if (testCase["created_by"] != null)
                        {
                            int?createdById = testCase["created_by"].Value <int?>();
                            if (createdById.HasValue && this.usersMapping.ContainsKey(createdById.Value))
                            {
                                remoteTestCase.AuthorId = this.usersMapping[createdById.Value];
                            }
                        }

                        //See if we have a suite or section to put it under (section takes precedence)
                        int?testSuiteId = null;
                        if (testCase["suite_id"] != null)
                        {
                            testSuiteId = testCase["suite_id"].Value <int?>();
                        }
                        int?testSectionId = null;
                        if (testCase["section_id"] != null)
                        {
                            testSectionId = testCase["section_id"].Value <int?>();
                        }

                        if (testSectionId.HasValue && this.testSuiteSectionMapping.ContainsKey(testSectionId.Value))
                        {
                            remoteTestCase.TestCaseFolderId = this.testSuiteSectionMapping[testSectionId.Value];
                        }
                        else if (testSuiteId.HasValue && this.testSuiteMapping.ContainsKey(testSuiteId.Value))
                        {
                            remoteTestCase.TestCaseFolderId = this.testSuiteMapping[testSuiteId.Value];
                        }
                        int newTestCaseId = spiraClient.TestCase_Create(remoteTestCase).TestCaseId.Value;
                        streamWriter.WriteLine("Added test case: " + testRailId);

                        //Add the test case to the release (if specified)
                        int?milestoneId = testCase["milestone_id"].Value <int?>();
                        if (milestoneId.HasValue && this.releaseMapping.ContainsKey(milestoneId.Value))
                        {
                            int releaseId = this.releaseMapping[milestoneId.Value];
                            spiraClient.Release_AddTestMapping(new RemoteReleaseTestCaseMapping()
                            {
                                ReleaseId = releaseId, TestCaseId = newTestCaseId
                            });
                        }

                        //Now see if we have steps (custom steps in TestRail)
                        JArray testSteps = testCase["custom_steps_separated"].Value <JArray>();
                        if (testSteps != null && testSteps.Count > 0)
                        {
                            streamWriter.WriteLine("Adding " + testSteps.Count + " custom steps to test case: " + testRailId);
                            foreach (JObject testStep in testSteps)
                            {
                                RemoteTestStep remoteTestStep = new RemoteTestStep();
                                remoteTestStep.Description    = testStep["content"].Value <string>();
                                remoteTestStep.ExpectedResult = testStep["expected"].Value <string>();
                                spiraClient.TestCase_AddStep(remoteTestStep, newTestCaseId);
                            }
                        }
                        streamWriter.WriteLine("Added custom steps to test case: " + testRailId);

                        //Add to the mapping hashtables
                        if (!this.testCaseMapping.ContainsKey(testRailId))
                        {
                            this.testCaseMapping.Add(testRailId, newTestCaseId);
                        }
                    }
                }
            }
        }
コード例 #14
0
        /// <summary>
        /// This method is responsible for actually importing the data
        /// </summary>
        /// <param name="stateInfo">State information handle</param>
        /// <remarks>This runs in background thread to avoid freezing the progress form</remarks>
        public void ImportData(object stateInfo)
        {
            //First open up the textfile that we will log information to (used for debugging purposes)
            string       debugFile    = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\Spira_TestRail_Import.log";
            StreamWriter streamWriter = File.CreateText(debugFile);

            try
            {
                streamWriter.WriteLine("Starting import at: " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());

                //Create the mapping hashtables
                this.testCaseMapping              = new Dictionary <int, int>();
                this.testSuiteMapping             = new Dictionary <int, int>();
                this.testSuiteSectionMapping      = new Dictionary <int, int>();
                this.testStepMapping              = new Dictionary <int, int>();
                this.usersMapping                 = new Dictionary <int, int>();
                this.testRunStepMapping           = new Dictionary <int, int>();
                this.testRunStepToTestStepMapping = new Dictionary <int, int>();
                this.testSetMapping               = new Dictionary <int, int>();
                this.testSetTestCaseMapping       = new Dictionary <int, int>();

                //Connect to Spira
                streamWriter.WriteLine("Connecting to Spira...");
                SpiraSoapService.SoapServiceClient spiraClient = new SpiraSoapService.SoapServiceClient();

                //Set the end-point and allow cookies
                string url = Properties.Settings.Default.SpiraUrl + ImportForm.IMPORT_WEB_SERVICES_URL;
                spiraClient.Endpoint.Address = new EndpointAddress(url);
                BasicHttpBinding httpBinding = (BasicHttpBinding)spiraClient.Endpoint.Binding;
                ConfigureBinding(httpBinding, spiraClient.Endpoint.Address.Uri);

                bool success = spiraClient.Connection_Authenticate2(Properties.Settings.Default.SpiraUserName, Properties.Settings.Default.SpiraPassword, "TestRailImporter");
                if (!success)
                {
                    string message = "Failed to authenticate with Spira using login: '******' so terminating import!";
                    streamWriter.WriteLine(message);
                    streamWriter.Close();

                    //Display the exception message
                    this.progressForm.ProgressForm_OnError(message);
                    return;
                }

                //Connect to TestRail
                APIClient testRailApi = new APIClient(Properties.Settings.Default.TestRailUrl);
                testRailApi.User     = Properties.Settings.Default.TestRailUserName;
                testRailApi.Password = Properties.Settings.Default.TestRailPassword;

                //1) Create a new project
                ImportProject(streamWriter, spiraClient, testRailApi);

                //2) Get the users and import - if we don't want to import user, map all TestRail users to single SpiraId
                int    userId      = -1;
                string newPassword = Properties.Settings.Default.NewUserPassword;
                if (!Properties.Settings.Default.Users)
                {
                    RemoteUser remoteUser = new RemoteUser();
                    remoteUser.FirstName    = "Test";
                    remoteUser.LastName     = "Rail";
                    remoteUser.UserName     = "******";
                    remoteUser.EmailAddress = "*****@*****.**";
                    remoteUser.Active       = true;
                    remoteUser.Approved     = true;
                    remoteUser.Admin        = false;
                    userId = spiraClient.User_Create(remoteUser, newPassword, "What was my default email address?", remoteUser.EmailAddress, PROJECT_ROLE_ID).UserId.Value;
                }

                ImportUsers(streamWriter, spiraClient, testRailApi, userId);

                //**** Show that we've imported users ****
                if (Properties.Settings.Default.Users)
                {
                    streamWriter.WriteLine("Users Imported");
                    this.progressForm.ProgressForm_OnProgressUpdate(1);
                }

                //3) Get the releases and import
                if (Properties.Settings.Default.Releases)
                {
                    ImportReleases(streamWriter, spiraClient, testRailApi);
                }

                //**** Show that we've imported releases ****
                streamWriter.WriteLine("Releases Imported");
                this.progressForm.ProgressForm_OnProgressUpdate(2);

                //3-4) Import the test suites and test cases
                if (Properties.Settings.Default.TestCases)
                {
                    ImportTestSuites(streamWriter, spiraClient, testRailApi);

                    //**** Show that we've imported test cases and test steps ****
                    streamWriter.WriteLine("Test Suites Imported");
                    this.progressForm.ProgressForm_OnProgressUpdate(3);

                    ImportTestCases(streamWriter, spiraClient, testRailApi);

                    //**** Show that we've imported test cases and test steps ****
                    streamWriter.WriteLine("Test Cases and Custom Steps Imported");
                    this.progressForm.ProgressForm_OnProgressUpdate(4);
                }

                //5) Import the test plans (check for case import)
                if (Properties.Settings.Default.TestCases)
                {
                    ImportTestPlans(streamWriter, spiraClient, testRailApi);

                    //**** Show that we've imported test cases and test steps ****
                    streamWriter.WriteLine("Test Plans Imported");
                    this.progressForm.ProgressForm_OnProgressUpdate(5);
                }

                //6) Import the test runs (have to have case as well!)
                if (Properties.Settings.Default.TestCases && Properties.Settings.Default.TestRuns)
                {
                    ImportTestRuns(streamWriter, spiraClient, testRailApi);

                    //**** Show that we've imported test cases and test steps ****
                    streamWriter.WriteLine("Test Runs Imported");
                    this.progressForm.ProgressForm_OnProgressUpdate(6);
                }

                //**** Mark the form as finished ****
                streamWriter.WriteLine("Import completed at: " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());
                this.progressForm.ProgressForm_OnFinish();

                //Close the debugging file
                streamWriter.Close();
            }
            catch (Exception exception)
            {
                //Log the error
                streamWriter.WriteLine("*ERROR* Occurred during Import: '" + exception.Message + "' at " + exception.Source + " (" + exception.StackTrace + ")");
                streamWriter.Close();

                //Display the exception message
                this.progressForm.ProgressForm_OnError(exception);
            }
        }
コード例 #15
0
        /// <summary>
        /// Imports the test suites and sections
        /// </summary>
        /// <param name="streamWriter"></param>
        /// <param name="spiraClient"></param>
        /// <param name="testRailApi"></param>
        private void ImportTestSuites(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi)
        {
            //Get the test cases from the TestRail API
            JArray testSuites = (JArray)testRailApi.SendGet("get_suites/" + this.testRailProjectId);

            if (testSuites != null)
            {
                foreach (JObject testSuite in testSuites)
                {
                    //Extract the user data
                    int testRailId = testSuite["id"].Value <int>();

                    //Create the new SpiraTest test folder
                    RemoteTestCaseFolder remoteTestFolder = new RemoteTestCaseFolder();
                    remoteTestFolder.Name        = testSuite["name"].Value <string>();
                    remoteTestFolder.Description = testSuite["description"].Value <string>();
                    int newTestCaseFolderId = spiraClient.TestCase_CreateFolder(remoteTestFolder).TestCaseFolderId.Value;
                    streamWriter.WriteLine("Added test suite: " + testRailId);

                    //Add to the mapping hashtables
                    if (!this.testSuiteMapping.ContainsKey(testRailId))
                    {
                        this.testSuiteMapping.Add(testRailId, newTestCaseFolderId);
                    }

                    //Now get each section in the suite
                    JArray testSections = (JArray)testRailApi.SendGet("get_sections/" + this.testRailProjectId + "&suite_id=" + testRailId);
                    if (testSections != null)
                    {
                        Dictionary <string, int> usedSectionNames = new  Dictionary <string, int>();
                        foreach (JObject testSection in testSections)
                        {
                            //Extract the user data
                            int testRailSectionId = testSection["id"].Value <int>();

                            //See if we have a name for this already in this suite, if so, re-use the existing section name
                            string sectionName = testSection["name"].Value <string>();
                            if (usedSectionNames.ContainsKey(sectionName))
                            {
                                //Add to the mapping dictionary
                                if (!this.testSuiteSectionMapping.ContainsKey(testRailSectionId))
                                {
                                    this.testSuiteSectionMapping.Add(testRailSectionId, usedSectionNames[sectionName]);
                                }
                            }
                            else
                            {
                                //Create the new SpiraTest test folder
                                remoteTestFolder             = new RemoteTestCaseFolder();
                                remoteTestFolder.Name        = sectionName;
                                remoteTestFolder.Description = testSection["description"].Value <string>();

                                //See if we have a parent section already imported
                                int?parentSectionId = testSection["parent_id"].Value <int?>();
                                if (parentSectionId.HasValue && this.testSuiteSectionMapping.ContainsKey(parentSectionId.Value))
                                {
                                    remoteTestFolder.ParentTestCaseFolderId = this.testSuiteSectionMapping[parentSectionId.Value];
                                }
                                else
                                {
                                    //Otherwise just import directly under the suite
                                    remoteTestFolder.ParentTestCaseFolderId = newTestCaseFolderId;
                                }

                                int newTestCaseFolderId2 = spiraClient.TestCase_CreateFolder(remoteTestFolder).TestCaseFolderId.Value;
                                streamWriter.WriteLine("Added test section: " + testRailSectionId);
                                usedSectionNames.Add(sectionName, newTestCaseFolderId2);

                                //Add to the mapping dictionary
                                if (!this.testSuiteSectionMapping.ContainsKey(testRailSectionId))
                                {
                                    this.testSuiteSectionMapping.Add(testRailSectionId, newTestCaseFolderId2);
                                }
                            }
                        }
                    }
                }
            }
        }
コード例 #16
0
        /// <summary>
        /// Imports a single test run with its tests
        /// </summary>
        private void ImportTestRun(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi, JObject testRun)
        {
            //Extract the test rail data
            if (testRun["id"].Type == JTokenType.Integer)
            {
                int testRailRunId = testRun["id"].Value <int>();
                streamWriter.WriteLine("Importing test run: " + testRailRunId);

                int?   milestoneId = testRun["milestone_id"].Value <int?>();
                int?   planId      = testRun["plan_id"].Value <int?>();
                string name        = testRun["name"].Value <string>();
                string description = testRun["description"].Value <string>();

                //See if we have any tests in the run
                JArray tests = (JArray)testRailApi.SendGet("get_tests/" + testRailRunId);
                if (tests != null)
                {
                    foreach (JObject test in tests)
                    {
                        int trTestId = test["id"].Value <int>();
                        streamWriter.WriteLine("Importing test: T" + trTestId);
                        int    trTestCaseId = test["case_id"].Value <int>();
                        int    trStatusId   = test["status_id"].Value <int>();
                        string title        = test["title"].Value <string>();

                        int?spiraTesterId          = null;
                        int?spiraReleaseId         = null;
                        int spiraTestCaseId        = this.testCaseMapping[trTestCaseId];
                        int?spiraTestSetId         = null;
                        int spiraExecutionStatusId = ConvertExecutionStatus(trStatusId);

                        int?assignedToId = test["assignedto_id"].Value <int?>();
                        if (assignedToId.HasValue && this.usersMapping.ContainsKey(assignedToId.Value))
                        {
                            spiraTesterId = this.usersMapping[assignedToId.Value];
                        }
                        if (milestoneId.HasValue && this.releaseMapping.ContainsKey(milestoneId.Value))
                        {
                            spiraReleaseId = this.releaseMapping[milestoneId.Value];
                        }
                        if (planId.HasValue && this.testSetMapping.ContainsKey(planId.Value))
                        {
                            spiraTestSetId = this.testSetMapping[planId.Value];
                        }

                        //Now get the results for the test
                        JArray results = (JArray)testRailApi.SendGet("get_results/" + trTestId);
                        if (results != null)
                        {
                            foreach (JObject result in results)
                            {
                                int trResultId = result["id"].Value <int>();
                                streamWriter.WriteLine("Importing result: " + trResultId);

                                DateTime executionDate = FromUnixTime(result["created_on"].Value <long>());
                                //streamWriter.WriteLine("*DEBUG*: " + executionDate.ToString() + "==" + result["created_on"].Value<long>());
                                string actualResults = result["comment"].Value <string>();

                                //We now upload each of these as a test run to SpiraTest

                                //Now create a new test run shell from this test case
                                RemoteManualTestRun[] remoteTestRuns = spiraClient.TestRun_CreateFromTestCases(new int[] { spiraTestCaseId }, null);
                                if (remoteTestRuns.Length > 0)
                                {
                                    //We concatenate the test run name and test title from TestRail
                                    string testRunName = name + ": " + title;

                                    //Update the test run information
                                    RemoteManualTestRun remoteTestRun = remoteTestRuns[0];
                                    remoteTestRun.ExecutionStatusId = spiraExecutionStatusId;
                                    remoteTestRun.StartDate         = executionDate;
                                    DateTime endDate = executionDate;   //We don't have a good way to parse TestRail durations
                                    remoteTestRun.EndDate   = endDate;
                                    remoteTestRun.Name      = testRunName;
                                    remoteTestRun.TestSetId = spiraTestSetId;
                                    //remoteTestRun.TestSetTestCaseId = testSetTestCaseId;  //No equivalent in TestRail
                                    remoteTestRun.TesterId  = spiraTesterId;
                                    remoteTestRun.ReleaseId = spiraReleaseId;

                                    //Now the steps
                                    foreach (RemoteTestRunStep remoteTestRunStep in remoteTestRun.TestRunSteps)
                                    {
                                        remoteTestRunStep.ActualResult      = actualResults;
                                        remoteTestRunStep.ExecutionStatusId = spiraExecutionStatusId;
                                    }

                                    //Save the run
                                    spiraClient.TestRun_Save(remoteTestRuns, executionDate);
                                    streamWriter.WriteLine("Added manual test run for result: " + trResultId);
                                }
                            }
                        }
                    }
                }
            }
        }
コード例 #17
0
        /// <summary>
        /// Logs into the specified project/domain combination
        /// </summary>
        /// <param name="sender">The sending object</param>
        /// <param name="e">The event arguments</param>
        private void btnLogin_Click(object sender, System.EventArgs e)
        {
            //Make sure that a login was entered
            if (this.txtLogin.Text.Trim() == "")
            {
                MessageBox.Show("You need to enter a SpiraTest login", "Authentication Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            //Make sure that a server was entered
            if (this.txtServer.Text.Trim() == "")
            {
                MessageBox.Show("You need to enter a SpiraTest web-server URL", "Authentication Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            //Store the info in settings for later
            Properties.Settings.Default.SpiraUrl      = this.txtServer.Text.Trim();
            Properties.Settings.Default.SpiraUserName = this.txtLogin.Text.Trim();
            if (chkPassword.Checked)
            {
                Properties.Settings.Default.SpiraPassword = this.txtPassword.Text;
            }
            else
            {
                Properties.Settings.Default.SpiraPassword = "";
            }
            Properties.Settings.Default.Save();

            //Put the password into settings after save so that we can use for the current import run regardless
            Properties.Settings.Default.SpiraPassword = this.txtPassword.Text;

            //Default the Start Import and login button to disabled
            this.btnImport.Enabled  = false;
            this.btnLogin.Enabled   = false;
            this.progressBar1.Style = ProgressBarStyle.Marquee;

            try
            {
                //Instantiate the web-service proxy class and set the URL from the text box
                SpiraImportProxy = new SpiraSoapService.SoapServiceClient();

                //Set the end-point and allow cookies
                string url = Properties.Settings.Default.SpiraUrl + IMPORT_WEB_SERVICES_URL;
                SpiraImportProxy.Endpoint.Address = new EndpointAddress(url);
                BasicHttpBinding httpBinding = (BasicHttpBinding)SpiraImportProxy.Endpoint.Binding;
                ConfigureBinding(httpBinding, SpiraImportProxy.Endpoint.Address.Uri);

                //Authenticate asynchronously
                SpiraImportProxy.Connection_AuthenticateCompleted += new EventHandler <SpiraSoapService.Connection_AuthenticateCompletedEventArgs>(spiraImportExport_Connection_AuthenticateCompleted);
                SpiraImportProxy.Connection_AuthenticateAsync(Properties.Settings.Default.SpiraUserName, this.txtPassword.Text);
            }
            catch (UriFormatException)
            {
                MessageBox.Show("You need to enter a valid URL!", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.progressBar1.Style = ProgressBarStyle.Blocks;
                this.btnLogin.Enabled   = true;
            }
            catch (TimeoutException exception)
            {
                // Handle the timeout exception.
                this.progressBar1.Style = ProgressBarStyle.Blocks;
                this.btnLogin.Enabled   = true;
                SpiraImportProxy.Abort();
                MessageBox.Show("A timeout error occurred! (" + exception.Message + ")", "Timeout Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (CommunicationException exception)
            {
                // Handle the communication exception.
                this.progressBar1.Style = ProgressBarStyle.Blocks;
                this.btnLogin.Enabled   = true;

                SpiraImportProxy.Abort();
                MessageBox.Show("A communication error occurred! (" + exception.Message + ")", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
コード例 #18
0
        /// <summary>
        /// Imports test cases and associated steps
        /// </summary>
        private void ImportTestCasesAndSteps(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, TestLink testLinkApi)
        {
            //Loop through all the mapped, imported test suites
            foreach (KeyValuePair <int, int> kvp in this.testSuiteMapping)
            {
                //Get the test cases from the TestLink API
                int testSuiteId  = kvp.Key;
                int testFolderId = kvp.Value;
                streamWriter.WriteLine(String.Format("Getting test cases in test suite: {0}", testSuiteId));
                List <TestCaseFromTestSuite> testCasesInSuite = testLinkApi.GetTestCasesForTestSuite(testSuiteId, true);
                if (testCasesInSuite != null)
                {
                    foreach (TestCaseFromTestSuite testCaseInSuite in testCasesInSuite)
                    {
                        //Extract the user data
                        int    tlTestCaseId         = testCaseInSuite.id;
                        string tlTestCaseExternalId = testCaseInSuite.external_id;
                        streamWriter.WriteLine(String.Format("Finding test case: {0} - {1}", tlTestCaseId, tlTestCaseExternalId));

                        //Get the actual test case object (with steps)
                        try
                        {
                            TestCase testCase = testLinkApi.GetTestCase(tlTestCaseId);
                            if (testCase != null)
                            {
                                streamWriter.WriteLine(String.Format("Importing test case: {0}", tlTestCaseId));
                                string description = "";
                                if (!String.IsNullOrEmpty(testCase.summary))
                                {
                                    description += "<p>" + testCase.summary + "</p>";
                                }
                                if (!String.IsNullOrEmpty(testCase.preconditions))
                                {
                                    description += "<p>" + testCase.preconditions + "</p>";
                                }

                                //Reauthenticate at this point, in case disconnected
                                spiraClient.Connection_Authenticate2(Properties.Settings.Default.SpiraUserName, Properties.Settings.Default.SpiraPassword, "TestLinkImporter");
                                spiraClient.Connection_ConnectToProject(this.newProjectId);

                                //Create the new SpiraTest test case
                                RemoteTestCase remoteTestCase = new RemoteTestCase();
                                remoteTestCase.Name             = testCase.name;
                                remoteTestCase.Description      = description;
                                remoteTestCase.TestCaseTypeId   = /* Functional */ 3;
                                remoteTestCase.TestCaseStatusId = (testCase.active) ? /* Ready For Test */ 5 : /* Draft*/ 1;
                                remoteTestCase.CreationDate     = testCase.creation_ts;
                                if (testCase.importance >= 1 && testCase.importance <= 4)
                                {
                                    remoteTestCase.TestCasePriorityId = testCase.importance;
                                }

                                //We don't import the author currently
                                //remoteTestCase.AuthorId = this.usersMapping[createdById.Value];

                                //Map to the folder
                                remoteTestCase.TestCaseFolderId = testFolderId;

                                int newTestCaseId = spiraClient.TestCase_Create(remoteTestCase).TestCaseId.Value;
                                streamWriter.WriteLine("Added test case: " + tlTestCaseId);

                                //See if we have any test steps for this test case
                                if (testCase.steps != null && testCase.steps.Count > 0)
                                {
                                    streamWriter.WriteLine("Adding " + testCase.steps.Count + " test steps to test case: " + tlTestCaseId);
                                    foreach (TestStep testStep in testCase.steps)
                                    {
                                        RemoteTestStep remoteTestStep = new RemoteTestStep();
                                        remoteTestStep.Description    = testStep.actions;
                                        remoteTestStep.ExpectedResult = testStep.expected_results;
                                        spiraClient.TestCase_AddStep(remoteTestStep, newTestCaseId);
                                    }
                                    streamWriter.WriteLine("Added test steps to test case: " + tlTestCaseId);
                                }

                                //Add to the mapping hashtables
                                if (!this.testCaseMapping.ContainsKey(tlTestCaseId))
                                {
                                    this.testCaseMapping.Add(tlTestCaseId, newTestCaseId);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            streamWriter.WriteLine(String.Format("Error importing test case {0}: {1} ({2})", tlTestCaseId, ex.Message, ex.StackTrace));
                        }
                    }
                }
            }
        }
コード例 #19
0
        private void ImportReleases(StreamWriter streamWriter, SpiraSoapService.SoapServiceClient spiraClient, APIClient testRailApi)
        {
            //Get the milestones from the TestRail API
            JArray milestones = (JArray)testRailApi.SendGet("get_milestones/" + this.testRailProjectId);

            if (milestones != null)
            {
                foreach (JObject milestone in milestones)
                {
                    try
                    {
                        //Extract the user data
                        int testRailId = milestone["id"].Value <int>();

                        //Load the release and capture the ID
                        RemoteRelease remoteRelease = new RemoteRelease();
                        remoteRelease.Name            = milestone["name"].Value <string>();
                        remoteRelease.Description     = (milestone["description"] == null) ? null : milestone["description"].Value <string>();
                        remoteRelease.VersionNumber   = testRailId.ToString();  //Use the test rail ID as the 'version number
                        remoteRelease.StartDate       = (milestone["start_on"] == null) ? DateTime.UtcNow : FromUnixTime(milestone["start_on"].Value <long>());
                        remoteRelease.EndDate         = (milestone["due_on"] == null) ? DateTime.UtcNow.AddMonths(1) : FromUnixTime(milestone["due_on"].Value <long>());
                        remoteRelease.ResourceCount   = 1;
                        remoteRelease.ReleaseTypeId   = /* Major Release */ 1;
                        remoteRelease.ReleaseStatusId = /* Planned */ 1;
                        bool isStarted   = (milestone["is_started"] == null) ? false : milestone["is_started"].Value <bool>();
                        bool isCompleted = (milestone["is_completed"] == null) ? false : milestone["is_completed"].Value <bool>();
                        if (isCompleted)
                        {
                            remoteRelease.ReleaseStatusId = /* Completed */ 3;
                        }
                        else if (isStarted)
                        {
                            remoteRelease.ReleaseStatusId = /* In Progress */ 2;
                        }

                        //See if we have a matching parent release
                        int?parentReleaseId = null;
                        if (milestone["parent_id"] != null)
                        {
                            int?testRailParentMilestoneId = milestone["parent_id"].Value <int?>();
                            if (testRailParentMilestoneId.HasValue && this.releaseMapping.ContainsKey(testRailParentMilestoneId.Value))
                            {
                                parentReleaseId = this.releaseMapping[testRailParentMilestoneId.Value];
                            }
                        }

                        int newReleaseId = spiraClient.Release_Create(remoteRelease, parentReleaseId).ReleaseId.Value;
                        streamWriter.WriteLine("Added release: " + testRailId);

                        //Add to the mapping hashtables
                        if (!this.releaseMapping.ContainsKey(testRailId))
                        {
                            this.releaseMapping.Add(testRailId, newReleaseId);
                        }
                    }
                    catch (Exception ex)
                    {
                        streamWriter.WriteLine("Ignoring TestRail milestone: " + milestone.ToString() + " - " + ex.Message);
                    }
                }
            }
        }