I ended up using Rally's API which seems to be the only reasonably practical way to do this. The code looked like this (C#):
// ...
Request folderRequest = new Request("TestFolder");
folderRequest.Fetch = new List<string>() { "Name", "FormattedID", "Parent" };
folderRequest.Query = new Query("FormattedID", Query.Operator.Equals, folder.Source);
//folderRequest.Workspace = workspaceRef;
QueryResult folderResult = restApi.Query(folderRequest);
var existingFolder = folderResult.Results.First();
Log("Copying from: " + existingFolder["FormattedID"] + " " + existingFolder["Name"] + " (parent: " + existingFolder["Parent"] + ")");
// get destination Folder
folderRequest = new Request("TestFolder");
folderRequest.Fetch = new List<string>() { "Name", "FormattedID", "Parent" };
folderRequest.Query = new Query("FormattedID", Query.Operator.Equals, folder.Destination);
QueryResult newFolderResult = restApi.Query(folderRequest);
var targetFolder = newFolderResult.Results.First();
var targetFolderRef = targetFolder["_ref"];
Log("Copying to: " + targetFolder["FormattedID"] + " " + targetFolder["Name"] + newFolderResult.TotalResultCount);
// Get the test cases
Request request = new Request("TestCase");
request.Fetch = new List<string>() { "Name", "Description", "FormattedID", "Objective", "PreConditions", "PostConditions",
"ValidationInput", "ValidationExpectedResult", "Owner", "Risk", "Priority", "Type" };
request.Query = new Query("TestFolder.FormattedID", Query.Operator.Equals, folder.Source);
QueryResult queryResult = restApi.Query(request);
if (queryResult.TotalResultCount == 0)
{
Log("Did not find any test case!");
return;
}
foreach (var sourceTC in queryResult.Results)
{
Log("TC: " + sourceTC["FormattedID"] + " Name: " + sourceTC["Name"]); // + " Description: " + result["Description"]);
// Create a new Test Case
DynamicJsonObject targetTC = new DynamicJsonObject();
targetTC["Name"] = sourceTC["Name"];
// Populate field data from Source to Target
targetTC["Name"] = sourceTC["Name"];
targetTC["Description"] = sourceTC["Description"];
targetTC["Owner"] = sourceTC["Owner"];
targetTC["Objective"] = sourceTC["Objective"];
targetTC["Type"] = sourceTC["Type"];
targetTC["Risk"] = sourceTC["Risk"];
targetTC["Priority"] = sourceTC["Priority"];
targetTC["PostConditions"] = sourceTC["PostConditions"];
targetTC["PreConditions"] = sourceTC["PreConditions"];
targetTC["ValidationInput"] = sourceTC["ValidationInput"];
targetTC["ValidationExpectedResult"] = sourceTC["ValidationExpectedResult"];
targetTC["TestFolder"] = targetFolderRef;
CreateResult createResult = restApi.Create("TestCase", targetTC);
// Get the item to make sure it was created
DynamicJsonObject item = restApi.GetByReference(createResult.Reference);
Log("TC was created: " + item.Fields.ToString());
// Get existing test steps and copy
Request stepRequest = new Request("TestCaseStep");
stepRequest.Fetch = new List<string>() { "TestCase", "StepIndex", "Input", "ExpectedResult" };
stepRequest.Query = new Query("TestCase.FormattedID", Query.Operator.Equals, sourceTC["FormattedID"]);
QueryResult stepsResult = restApi.Query(stepRequest);
foreach(var step in stepsResult.Results)
{
Log(step["StepIndex"] + ": " +
step["Input"] + ":" + step["ExpectedResult"]);
DynamicJsonObject targetStep = new DynamicJsonObject();
targetStep["StepIndex"] = step["StepIndex"];
targetStep["Input"] = step["Input"];
targetStep["ExpectedResult"] = step["ExpectedResult"];
targetStep["TestCase"] = createResult.Reference;
CreateResult createStep = restApi.Create("TestCaseStep", targetStep);
}
}