/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using NUnit.Framework;
using QuantConnect.Api;
using QuantConnect.Configuration;
using QuantConnect.Logging;
using System;
using System.Collections;
using System.IO;
using System.Text.Json;
using System.Threading;
namespace QuantConnect.Tests.API
{
///
/// Base test class for Api tests, provides the setup needed for all api tests
///
public class ApiTestBase
{
internal int TestAccount;
internal string TestToken;
internal string TestOrganization;
internal string DataFolder;
internal Api.Api ApiClient;
protected Project TestProject { get; private set; }
protected Backtest TestBacktest { get; private set; }
///
/// Run once before any RestApiTests
///
[OneTimeSetUp]
public void Setup()
{
ReloadConfiguration();
TestAccount = Globals.UserId;
TestToken = Globals.UserToken;
TestOrganization = Globals.OrganizationID;
DataFolder = Globals.DataFolder;
ApiClient = new Api.Api();
ApiClient.Initialize(TestAccount, TestToken, DataFolder);
// Let's create a project and backtest that can be used for general testing
CreateTestProjectAndBacktest();
}
[OneTimeTearDown]
public void TearDown()
{
DeleteTestProjectAndBacktest();
}
private void CreateTestProjectAndBacktest()
{
// Create a new project and backtest that can be used for testing
Log.Debug("ApiTestBase.Setup(): Creating test project and backtest");
var createProjectResult = ApiClient.CreateProject($"TestProject{DateTime.UtcNow.ToStringInvariant("yyyyMMddHHmmssfff")}",
Language.CSharp, TestOrganization);
if (!createProjectResult.Success)
{
Assert.Warn("Could not create test project, tests using it will fail.");
return;
}
TestProject = createProjectResult.Projects[0];
// Create a new compile for the project
Log.Debug("ApiTestBase.Setup(): Creating test compile");
var compile = ApiClient.CreateCompile(TestProject.ProjectId);
if (!compile.Success)
{
Assert.Warn("Could not create compile for the test project, tests using it will fail.");
return;
}
Log.Debug("ApiTestBase.Setup(): Waiting for test compile to complete");
compile = WaitForCompilerResponse(ApiClient, TestProject.ProjectId, compile.CompileId);
if (!compile.Success)
{
Assert.Warn("Could not create compile for the test project, tests using it will fail.");
return;
}
// Create a backtest
Log.Debug("ApiTestBase.Setup(): Creating test backtest");
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("u")} API Backtest";
var backtest = ApiClient.CreateBacktest(TestProject.ProjectId, compile.CompileId, backtestName);
if (!backtest.Success)
{
Assert.Warn("Could not create backtest for the test project, tests using it will fail.");
return;
}
Log.Debug("ApiTestBase.Setup(): Waiting for test backtest to complete");
TestBacktest = WaitForBacktestCompletion(ApiClient, TestProject.ProjectId, backtest.BacktestId);
if (!TestBacktest.Success)
{
Assert.Warn("Could not create backtest for the test project, tests using it will fail.");
return;
}
Log.Debug("ApiTestBase.Setup(): Test project and backtest created successfully");
}
private void DeleteTestProjectAndBacktest()
{
if (TestBacktest != null && TestBacktest.Success)
{
Log.Debug("ApiTestBase.TearDown(): Deleting test backtest");
ApiClient.DeleteBacktest(TestProject.ProjectId, TestBacktest.BacktestId);
}
if (TestProject != null)
{
Log.Debug("ApiTestBase.TearDown(): Deleting test project");
ApiClient.DeleteProject(TestProject.ProjectId);
}
}
public static bool IsValidJson(string jsonString)
{
try
{
using (JsonDocument doc = JsonDocument.Parse(jsonString))
{
}
return true;
}
catch (JsonException)
{
return false;
}
}
///
/// Wait for the compiler to respond to a specified compile request
///
/// Id of the project
/// Id of the compilation of the project
///
public static Compile WaitForCompilerResponse(Api.Api apiClient, int projectId, string compileId, int seconds = 60)
{
var compile = new Compile();
var finish = DateTime.UtcNow.AddSeconds(seconds);
do
{
Thread.Sleep(100);
compile = apiClient.ReadCompile(projectId, compileId);
} while (compile.State == CompileState.InQueue && DateTime.UtcNow < finish);
return compile;
}
///
/// Wait for the backtest to complete
///
/// Project id to scan
/// Backtest id previously started
/// Completed backtest object
public static Backtest WaitForBacktestCompletion(Api.Api apiClient, int projectId, string backtestId, int secondsTimeout = 60, bool returnFailedBacktest = false)
{
Backtest backtest;
var finish = DateTime.UtcNow.AddSeconds(secondsTimeout);
do
{
Thread.Sleep(1000);
backtest = apiClient.ReadBacktest(projectId, backtestId);
if (backtest == null)
{
// api failed, let's retry
continue;
}
if (!string.IsNullOrEmpty(backtest.Error) || backtest.HasInitializeError)
{
if (!returnFailedBacktest)
{
Assert.Fail($"Backtest {projectId}/{backtestId} failed: {backtest.Error}. Stacktrace: {backtest.Stacktrace}. Api errors: {string.Join(",", backtest.Errors)}");
}
else
{
return backtest;
}
}
} while (((backtest == null || (backtest.Success && backtest.Progress < 1)) && DateTime.UtcNow < finish));
return backtest;
}
///
/// Reload configuration, making sure environment variables are loaded into the config
///
internal static void ReloadConfiguration()
{
// nunit 3 sets the current folder to a temp folder we need it to be the test bin output folder
var dir = TestContext.CurrentContext.TestDirectory;
Environment.CurrentDirectory = dir;
Directory.SetCurrentDirectory(dir);
// reload config from current path
Config.Reset();
var environment = Environment.GetEnvironmentVariables();
foreach (DictionaryEntry entry in environment)
{
var envKey = entry.Key.ToString();
var value = entry.Value.ToString();
if (envKey.StartsWith("QC_", StringComparison.InvariantCulture))
{
var key = envKey.Substring(3).Replace("_", "-", StringComparison.InvariantCulture).ToLowerInvariant();
Log.Trace($"TestSetup(): Updating config setting '{key}' from environment var '{envKey}'");
Config.Set(key, value);
}
}
// resets the version among other things
Globals.Reset();
}
}
}