diff --git a/.travis.yml b/.travis.yml
index 14242d1..c47ddb4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: csharp
mono: none
-dotnet: 2.1.810
+dotnet: 5.0.2
script:
- dotnet restore
- dotnet build ./RedcapApi/
diff --git a/README.md b/README.md
index e8335c2..fa1f9bd 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,10 @@ __Prerequisites__
5. Build the solution, then run the tests
__API METHODS SUPPORTED (Not all listed)__
+* ExportLoggingAsync
+* ExportDagsAsync
+* ImportDagsAsync
+* DeleteDagsAsync
* ExportArmsAsync
* ImportArmsAsync
* DeleteArmsAsync
@@ -54,7 +58,7 @@ namespace RedcapApiDemo
Console.WriteLine("Redcap Api Demo Started!");
// Use your own API Token here...
var apiToken = "3D57A7FA57C8A43F6C8803A84BB3957B";
- var redcap_api = new RedcapApi("http://localhost/redcap/api/");
+ var redcap_api = new RedcapApi("https://localhost/redcap/api/");
Console.WriteLine("Exporting all records from project.");
var result = redcap_api.ExportRecordsAsync(apiToken).Result;
@@ -73,21 +77,21 @@ __Install directly in Package Manager Console or Command Line Interface__
```C#
Package Manager
-Install-Package RedcapAPI -Version 1.0.9
+Install-Package RedcapAPI -Version 1.1.0
```
```C#
.NET CLI
-dotnet add package RedcapAPI --version 1.0.9
+dotnet add package RedcapAPI --version 1.1.0
```
```C#
Paket CLI
-paket add RedcapAPI --version 1.0.9
+paket add RedcapAPI --version 1.1.0
```
diff --git a/RedcapApi/Api/Redcap.cs b/RedcapApi/Api/Redcap.cs
index 034b855..da0b2c8 100644
--- a/RedcapApi/Api/Redcap.cs
+++ b/RedcapApi/Api/Redcap.cs
@@ -1,14 +1,18 @@
-using Newtonsoft.Json;
-using Redcap.Interfaces;
-using Redcap.Models;
-using Redcap.Utilities;
-using Serilog;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
+
+using Newtonsoft.Json;
+
+using Redcap.Interfaces;
+using Redcap.Models;
+using Redcap.Utilities;
+
+using Serilog;
+
using static System.String;
namespace Redcap
@@ -78,7 +82,314 @@ public RedcapApi(string redcapApiUrl, bool useInsecureCertificates = false)
}
#region API Version 1.0.0+ Begin
+ #region Logging
+ ///
+ /// API @Version 10.8
+ /// POST
+ /// Export Logging
+ /// This method allows you to export the logging (audit trail) of all changes made to this project, including data exports, data changes, project metadata changes, modification of user rights, etc.
+ ///
+ /// To use this method, you must have API Export privileges in the project.
+ ///
+ ///
+ /// The API token specific to your REDCap project and username (each token is unique to each user for each project). See the section on the left-hand menu for obtaining a token for a given project.
+ /// log
+ /// csv, json [default], xml
+ /// You may choose event type to fetch result for specific event type
+ /// To return only the events belong to specific user (referring to existing username), provide a user. If not specified, it will assume all users
+ /// To return only the events belong to specific record (referring to existing record name), provide a record. If not specified, it will assume all records. This parameter is available only when event is related to record.
+ /// To return only the events belong to specific DAG (referring to group_id), provide a dag. If not specified, it will assume all dags.
+ /// To return only the events that have been logged *after* a given date/time, provide a timestamp in the format YYYY-MM-DD HH:MM (e.g., '2017-01-01 17:00' for January 1, 2017 at 5:00 PM server time). If not specified, it will assume no begin time.
+ /// To return only records that have been logged *before* a given date/time, provide a timestamp in the format YYYY-MM-DD HH:MM (e.g., '2017-01-01 17:00' for January 1, 2017 at 5:00 PM server time). If not specified, it will use the current server time.
+ /// csv, json, xml - specifies the format of error messages. If you do not pass in this flag, it will select the default format for you passed based on the 'format' flag you passed in or if no format flag was passed in, it will default to 'json'.
+ /// List of all changes made to this project, including data exports, data changes, and the creation or deletion of users.
+ public async Task ExportLoggingAsync(string token, Content content, ReturnFormat format = ReturnFormat.json, LogType logType = LogType.All, string user = null, string record = null, string dag = null, string beginTime = null, string endTime = null, OnErrorFormat onErrorFormat = OnErrorFormat.json)
+ {
+ var exportLoggingResults = string.Empty;
+ try
+ {
+ // Check for presence of token
+ this.CheckToken(token);
+
+ var payload = new Dictionary
+ {
+ { "token", token },
+ { "content", content.GetDisplayName() },
+ { "format", format.GetDisplayName() },
+ { "returnFormat", onErrorFormat.GetDisplayName() },
+ { "logtype", logType.GetDisplayName() }
+ };
+
+ // Optional
+ if (!string.IsNullOrEmpty(user))
+ {
+ payload.Add("user", user);
+ }
+ if (!string.IsNullOrEmpty(record))
+ {
+ payload.Add("record", record);
+ }
+ if (!string.IsNullOrEmpty(dag))
+ {
+ payload.Add("dag", dag);
+ }
+ if (!string.IsNullOrEmpty(beginTime))
+ {
+ payload.Add("beginTime", beginTime);
+ }
+ if (!string.IsNullOrEmpty(endTime))
+ {
+ payload.Add("endTime", endTime);
+ }
+ exportLoggingResults = await this.SendPostRequestAsync(payload, _uri);
+ return exportLoggingResults;
+ }
+ catch (Exception Ex)
+ {
+ Log.Error($"{Ex.Message}");
+ return exportLoggingResults;
+ }
+ }
+ #endregion
+ #region Data Access Groups
+ ///
+ /// POST
+ /// Export DAGs
+ /// This method allows you to export the Data Access Groups for a project
+ ///
+ /// To use this method, you must have API Export privileges in the project.
+ ///
+ ///
+ /// The API token specific to your REDCap project and username (each token is unique to each user for each project). See the section on the left-hand menu for obtaining a token for a given project.
+ /// dag
+ /// csv, json [default], xml
+ /// csv, json, xml - specifies the format of error messages. If you do not pass in this flag, it will select the default format for you passed based on the 'format' flag you passed in or if no format flag was passed in, it will default to 'json'.
+ /// DAGs for the project in the format specified
+ public async Task ExportDagsAsync(string token, Content content, ReturnFormat format = ReturnFormat.json, OnErrorFormat onErrorFormat = OnErrorFormat.json)
+ {
+ var exportDagsResults = string.Empty;
+ try
+ {
+ // Check for presence of token
+ this.CheckToken(token);
+
+ // Request payload
+ var payload = new Dictionary
+ {
+ { "token", token },
+ { "content", content.GetDisplayName() },
+ { "format", format.GetDisplayName() },
+ { "returnFormat", onErrorFormat.GetDisplayName() }
+ };
+ exportDagsResults = await this.SendPostRequestAsync(payload, _uri);
+ return exportDagsResults;
+
+ }
+ catch (Exception Ex)
+ {
+ Log.Error($"{Ex.Message}");
+ return exportDagsResults;
+ }
+ }
+ ///
+ /// POST
+ /// Import DAGs
+ /// This method allows you to import new DAGs (Data Access Groups) into a project or update the group name of any existing DAGs.
+ /// NOTE: DAGs can be renamed by simply changing the group name(data_access_group_name).
+ /// DAG can be created by providing group name value while unique group name should be set to blank.
+ ///
+ ///
+ /// To use this method, you must have API Import/Update privileges in the project.
+ ///
+ /// The API token specific to your REDCap project and username (each token is unique to each user for each project). See the section on the left-hand menu for obtaining a token for a given project.
+ /// dags
+ /// import
+ /// csv, json [default], xml
+ /// Contains the attributes 'data_access_group_name' (referring to the group name) and 'unique_group_name' (referring to the auto-generated unique group name) of each DAG to be created/modified, in which they are provided in the specified format.
+ /// Refer to the API documenations for additional examples.
+ /// JSON Example:
+ /// [{"data_access_group_name":"CA Site","unique_group_name":"ca_site"}
+ /// {"data_access_group_name":"FL Site","unique_group_name":"fl_site"},
+ /// { "data_access_group_name":"New Site","unique_group_name":""}]
+ /// CSV Example:
+ /// data_access_group_name,unique_group_name
+ /// "CA Site",ca_site
+ /// "FL Site",fl_site
+ /// "New Site",
+ ///
+ /// csv, json, xml - specifies the format of error messages. If you do not pass in this flag, it will select the default format for you passed based on the 'format' flag you passed in or if no format flag was passed in, it will default to 'json'.
+ /// Number of DAGs added or updated
+ public async Task ImportDagsAsync(string token, Content content, RedcapAction action, ReturnFormat format, List data, OnErrorFormat onErrorFormat = OnErrorFormat.json)
+ {
+ var importDagsResults = string.Empty;
+ try
+ {
+ // Check for presence of token
+ this.CheckToken(token);
+ var _serializedData = JsonConvert.SerializeObject(data);
+ var payload = new Dictionary
+ {
+ { "token", token },
+ { "content", content.GetDisplayName() },
+ { "action", action.GetDisplayName() },
+ { "format", format.GetDisplayName() },
+ { "returnFormat", onErrorFormat.GetDisplayName() },
+ { "data", _serializedData }
+ };
+ // Execute request
+ importDagsResults = await this.SendPostRequestAsync(payload, _uri);
+ return importDagsResults;
+ }
+ catch (Exception Ex)
+ {
+ Log.Error($"{Ex.Message}");
+ return importDagsResults;
+ }
+ }
+ ///
+ /// POST
+ /// Delete DAGs
+ /// This method allows you to delete DAGs from a project.
+ ///
+ /// To use this method, you must have API Import/Update privileges in the project.
+ ///
+ ///
+ /// The API token specific to your REDCap project and username (each token is unique to each user for each project). See the section on the left-hand menu for obtaining a token for a given project.
+ /// dag
+ /// delete
+ /// an array of unique group names that you wish to delete
+ /// Number of DAGs deleted
+ public async Task DeleteDagsAsync(string token, Content content, RedcapAction action, string[] dags)
+ {
+ var deleteDagsResult = string.Empty;
+ try
+ {
+ // Check for presence of token
+ this.CheckToken(token);
+
+ // Check for any dags
+ if (dags.Length < 1)
+ {
+ throw new InvalidOperationException($"No dags to delete.");
+ }
+ var payload = new Dictionary
+ {
+ { "token", token },
+ { "content", content.GetDisplayName() },
+ { "action", action.GetDisplayName() }
+ };
+ // Required
+ for (var i = 0; i < dags.Length; i++)
+ {
+ payload.Add($"dags[{i}]", dags[i].ToString());
+ }
+ // Execute request
+ deleteDagsResult = await this.SendPostRequestAsync(payload, _uri);
+ return deleteDagsResult;
+ }
+ catch (Exception Ex)
+ {
+ Log.Error($"{Ex.Message}");
+ return deleteDagsResult;
+ }
+ }
+ ///
+ /// POST
+ /// Export User-DAG Assignments
+ /// This method allows you to export existing User-DAG assignments for a project
+ ///
+ /// To use this method, you must have API Export privileges in the project.
+ ///
+ ///
+ /// The API token specific to your REDCap project and username (each token is unique to each user for each project). See the section on the left-hand menu for obtaining a token for a given project.
+ /// userDagMapping
+ /// csv, json [default], xml
+ /// csv, json, xml - specifies the format of error messages. If you do not pass in this flag, it will select the default format for you passed based on the 'format' flag you passed in or if no format flag was passed in, it will default to 'json'.
+ /// User-DAG assignments for the project in the format specified
+ public async Task ExportUserDagAssignmentAsync(string token, Content content, ReturnFormat format = ReturnFormat.json, OnErrorFormat onErrorFormat = OnErrorFormat.json)
+ {
+ var exportUserDagAssignmentResult = string.Empty;
+ try
+ {
+ // Check for presence of token
+ this.CheckToken(token);
+
+ // Request payload
+ var payload = new Dictionary
+ {
+ { "token", token },
+ { "content", content.GetDisplayName() },
+ { "format", format.GetDisplayName() },
+ { "returnFormat", onErrorFormat.GetDisplayName() }
+ };
+ exportUserDagAssignmentResult = await this.SendPostRequestAsync(payload, _uri);
+ return exportUserDagAssignmentResult;
+
+ }
+ catch (Exception Ex)
+ {
+ Log.Error($"{Ex.Message}");
+ return exportUserDagAssignmentResult;
+ }
+ }
+ ///
+ /// POST
+ /// Import User-DAG Assignments
+ /// This method allows you to assign users to any data access group.
+ /// NOTE: If you wish to modify an existing mapping, you *must* provide its unique username and group name.If the 'redcap_data_access_group' column is not provided, user will not assigned to any group.There should be only one record per username.
+ ///
+ /// To use this method, you must have API Import/Update privileges in the project.
+ ///
+ ///
+ ///
+ /// The API token specific to your REDCap project and username (each token is unique to each user for each project). See the section on the left-hand menu for obtaining a token for a given project.
+ /// userDagMapping
+ /// import
+ /// csv, json [default], xml
+ ///
+ /// Contains the attributes 'username' (referring to the existing unique username) and 'redcap_data_access_group' (referring to existing unique group name) of each User-DAG assignments to be modified, in which they are provided in the specified format.
+ /// JSON Example:
+ /// [{"username":"ca_dt_person","redcap_data_access_group":"ca_site"},
+ /// {"username":"fl_dt_person","redcap_data_access_group":"fl_site"},
+ /// { "username":"global_user","redcap_data_access_group":""}]
+ /// CSV Example:
+ /// username,redcap_data_access_group
+ /// ca_dt_person, ca_site
+ /// fl_dt_person, fl_site
+ /// global_user,
+ ///
+ /// csv, json, xml - specifies the format of error messages. If you do not pass in this flag, it will select the default format for you passed based on the 'format' flag you passed in or if no format flag was passed in, it will default to 'json'.
+ /// Number of User-DAG assignments added or updated
+ public async Task ImportUserDagAssignmentAsync(string token, Content content, RedcapAction action, ReturnFormat format, List data, OnErrorFormat onErrorFormat = OnErrorFormat.json)
+ {
+ var ImportUserDagAssignmentResults = string.Empty;
+ try
+ {
+ // Check for presence of token
+ this.CheckToken(token);
+
+ var _serializedData = JsonConvert.SerializeObject(data);
+ var payload = new Dictionary
+ {
+ { "token", token },
+ { "content", content.GetDisplayName() },
+ { "action", action.GetDisplayName() },
+ { "format", format.GetDisplayName() },
+ { "returnFormat", onErrorFormat.GetDisplayName() },
+ { "data", _serializedData }
+ };
+ // Execute request
+ return await this.SendPostRequestAsync(payload, _uri);
+ }
+ catch (Exception Ex)
+ {
+ Log.Error($"{Ex.Message}");
+ return ImportUserDagAssignmentResults;
+ }
+ }
+
+ #endregion
#region Arms
///
/// API Version 1.0.0+ **
@@ -221,7 +532,7 @@ public async Task ExportArmsAsync(string token, Content content, ReturnF
///
/// csv, json, xml - specifies the format of error messages. If you do not pass in this flag, it will select the default format for you passed based on the 'format' flag you passed in or if no format flag was passed in, it will default to 'xml'.
/// Number of Arms imported
- public async Task ImportArmsAsync(string token, Override overrideBhavior, RedcapAction action, ReturnFormat format, List data, OnErrorFormat returnFormat)
+ public async Task ImportArmsAsync(string token, Override overrideBhavior, RedcapAction action, ReturnFormat format, List data, OnErrorFormat returnFormat = OnErrorFormat.json)
{
try
{
@@ -2966,6 +3277,7 @@ public async Task ImportRecordsAsync(string token, ReturnFormat forma
/// the content specified by returnContent
public async Task ImportRecordsAsync(string token, Content content, ReturnFormat format, RedcapDataType redcapDataType, OverwriteBehavior overwriteBehavior, bool forceAutoNumber, List data, string dateFormat = "", CsvDelimiter csvDelimiter = CsvDelimiter.tab, ReturnContent returnContent = ReturnContent.count, OnErrorFormat onErrorFormat = OnErrorFormat.json)
{
+ var importRecordsResults = string.Empty;
try
{
this.CheckToken(token);
@@ -2992,15 +3304,13 @@ public async Task ImportRecordsAsync(string token, Content content, R
{
payload.Add("returnContent", returnContent.ToString());
}
- return await this.SendPostRequestAsync(payload, _uri);
+ importRecordsResults = await this.SendPostRequestAsync(payload, _uri);
+ return importRecordsResults;
}
catch (Exception Ex)
{
- /*
- * We'll just log the error and return the error message.
- */
Log.Error($"{Ex.Message}");
- return Ex.Message;
+ return importRecordsResults;
}
}
@@ -3628,7 +3938,7 @@ public async Task ExportSurveyParticipantsAsync(string token, Content co
return Ex.Message;
}
}
-
+
///
/// API Version 1.0.0+
/// From Redcap Version 6.4.0
@@ -3713,7 +4023,7 @@ public async Task ExportSurveyQueueLinkAsync(string token, Content conte
return Ex.Message;
}
}
-
+
///
/// API Version 1.0.0+
/// From Redcap Version 6.4.0
diff --git a/RedcapApi/Broker/ApiBroker.cs b/RedcapApi/Broker/ApiBroker.cs
new file mode 100644
index 0000000..b5bdc97
--- /dev/null
+++ b/RedcapApi/Broker/ApiBroker.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using RestSharp;
+
+using Serilog;
+
+namespace Redcap.Broker
+{
+ public partial class ApiBroker: IApiBroker
+ {
+ protected readonly HttpClient httpClient;
+ protected readonly IRestClient restClient;
+ public ApiBroker(HttpClient httpClient, IRestClient restClient)
+ {
+ this.httpClient = httpClient;
+ this.restClient = restClient;
+ }
+ public void LogException(Exception ex,
+ [CallerMemberName] string method = null,
+ [CallerFilePath] string filePath = null,
+ [CallerLineNumber] int lineNumber = 0)
+ {
+ var errorMessage = $"Message: {ex.Message}. Method: {method} File: {filePath} LineNumber: {lineNumber}";
+ Log.Error($"Message: {ex.Message}. Method: {method} File: {filePath} LineNumber: {lineNumber}");
+ throw new Exception(errorMessage);
+ }
+ public async Task PostAsync(IRestRequest request, CancellationToken cancellationToken = default)
+ {
+ var response = await restClient.PostAsync(request, cancellationToken);
+
+ return response;
+ }
+ public async Task ExecuteAsync(RestRequest request) where T : new()
+ {
+ var response = await restClient.ExecuteAsync(request);
+ if(response.ErrorException != null)
+ {
+ LogException(response.ErrorException);
+ }
+ return response.Data;
+ }
+ }
+}
diff --git a/RedcapApi/Broker/IApiBroker.cs b/RedcapApi/Broker/IApiBroker.cs
new file mode 100644
index 0000000..18da857
--- /dev/null
+++ b/RedcapApi/Broker/IApiBroker.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using RestSharp;
+
+namespace Redcap.Broker
+{
+ public interface IApiBroker
+ {
+ Task ExecuteAsync(RestRequest request) where T : new();
+ void LogException(Exception ex, [CallerMemberName] string method = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0);
+ Task PostAsync(IRestRequest request, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/RedcapApi/Interfaces/IRedcap.cs b/RedcapApi/Interfaces/IRedcap.cs
index fb6823e..6ecc793 100644
--- a/RedcapApi/Interfaces/IRedcap.cs
+++ b/RedcapApi/Interfaces/IRedcap.cs
@@ -1484,5 +1484,11 @@ public interface IRedcap
/// MDY, DMY, YMD [default] - the format of values being imported for dates or datetime fields (understood with M representing 'month', D as 'day', and Y as 'year') - NOTE: The default format is Y-M-D (with dashes), while MDY and DMY values should always be formatted as M/D/Y or D/M/Y (with slashes), respectively.
/// Returns the content with format specified.
Task SaveRecordsAsync(List data, ReturnContent returnContent, OverwriteBehavior overwriteBehavior, ReturnFormat? inputFormat, RedcapDataType? redcapDataType, OnErrorFormat? returnFormat, string dateFormat = "MDY");
+ Task ExportLoggingAsync(string token, Content content, ReturnFormat format = ReturnFormat.json, LogType logType = LogType.All, string user = null, string record = null, string dag = null, string beginTime = null, string endTime = null, OnErrorFormat onErrorFormat = OnErrorFormat.json);
+ Task ExportDagsAsync(string token, Content content, ReturnFormat format = ReturnFormat.json, OnErrorFormat onErrorFormat = OnErrorFormat.json);
+ Task ImportDagsAsync(string token, Content content, RedcapAction action, ReturnFormat format, List data, OnErrorFormat onErrorFormat = OnErrorFormat.json);
+ Task DeleteDagsAsync(string token, Content content, RedcapAction action, string[] dags);
+ Task ExportUserDagAssignmentAsync(string token, Content content, ReturnFormat format = ReturnFormat.json, OnErrorFormat onErrorFormat = OnErrorFormat.json);
+ Task ImportUserDagAssignmentAsync(string token, Content content, RedcapAction action, ReturnFormat format, List data, OnErrorFormat onErrorFormat = OnErrorFormat.json);
}
}
diff --git a/RedcapApi/Models/Action.cs b/RedcapApi/Models/Action.cs
index 9386795..2bfb29b 100644
--- a/RedcapApi/Models/Action.cs
+++ b/RedcapApi/Models/Action.cs
@@ -7,7 +7,7 @@
namespace Redcap.Models
{
///
- /// API Action
+ /// API Action => Export, Import, Delete
///
public enum RedcapAction
{
diff --git a/RedcapApi/Models/Content.cs b/RedcapApi/Models/Content.cs
index 6687682..4f6cf1f 100644
--- a/RedcapApi/Models/Content.cs
+++ b/RedcapApi/Models/Content.cs
@@ -8,6 +8,21 @@ namespace Redcap.Models
///
public enum Content
{
+ ///
+ /// Log
+ ///
+ [Display(Name ="log")]
+ Log,
+ ///
+ /// User-Mapping
+ ///
+ [Display(Name = "userDagMapping")]
+ UserDagMapping,
+ ///
+ /// Dag Content
+ ///
+ [Display(Name = "dag")]
+ Dag,
///
/// Arm Content
///
diff --git a/RedcapApi/Models/Demographic.cs b/RedcapApi/Models/Demographic.cs
new file mode 100644
index 0000000..a432631
--- /dev/null
+++ b/RedcapApi/Models/Demographic.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+
+namespace Redcap.Models
+{
+ ///
+ /// Simplified demographics instrument that we can test with.
+ ///
+ public class Demographic
+ {
+ ///
+ ///
+ ///
+ [JsonRequired]
+ [JsonProperty("record_id")]
+ public string RecordId { get; set; }
+
+ ///
+ ///
+ ///
+ [JsonProperty("first_name")]
+ public string FirstName { get; set; }
+
+ ///
+ ///
+ ///
+ [JsonProperty("last_name")]
+ public string LastName { get; set; }
+ }
+}
diff --git a/RedcapApi/Models/LogType.cs b/RedcapApi/Models/LogType.cs
new file mode 100644
index 0000000..4c18153
--- /dev/null
+++ b/RedcapApi/Models/LogType.cs
@@ -0,0 +1,69 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Redcap.Models
+{
+ ///
+ /// Logging type
+ ///
+ public enum LogType
+ {
+ ///
+ /// Data Export
+ ///
+ [Display(Name = "export")]
+ Export,
+
+ ///
+ /// Manage/Design
+ ///
+ [Display(Name = "manage")]
+ Manage,
+
+ ///
+ /// User or role created-updated-deleted
+ ///
+ [Display(Name = "user")]
+ User,
+
+ ///
+ /// Record created-updated-deleted
+ ///
+ [Display(Name = "record")]
+ Record,
+
+ ///
+ /// Record created (only)
+ ///
+ [Display(Name = "record_add")]
+ RecordAdd,
+
+ ///
+ /// Record updated (only)
+ ///
+ [Display(Name = "record_edit")]
+ RecordEdit,
+
+ ///
+ /// Record deleted (only)
+ ///
+ [Display(Name = "record_delete")]
+ RecordDelete,
+
+ ///
+ /// Record locking and e-signatures
+ ///
+ [Display(Name ="lock_record")]
+ LockRecord,
+ ///
+ /// Page views
+ ///
+ ///
+ [Display(Name = "page_view")]
+ PageView,
+ ///
+ /// All event types (excluding page views)
+ ///
+ [Display(Name = "")]
+ All
+ }
+}
diff --git a/RedcapApi/Models/RedcapDag.cs b/RedcapApi/Models/RedcapDag.cs
new file mode 100644
index 0000000..13d17d2
--- /dev/null
+++ b/RedcapApi/Models/RedcapDag.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Newtonsoft.Json;
+
+namespace Redcap.Models
+{
+ ///
+ /// Data Access Group
+ /// Example:
+ /// [{"data_access_group_name":"CA Site","unique_group_name":"ca_site"}
+ /// {"data_access_group_name":"FL Site","unique_group_name":"fl_site"},
+ /// { "data_access_group_name":"New Site","unique_group_name":""}]
+ ///
+ public class RedcapDag
+ {
+ ///
+ /// group name
+ ///
+ ///
+ [JsonProperty("data_access_group_name")]
+ public string GroupName { get; set; }
+ ///
+ /// auto-generated unique group name
+ ///
+ ///
+ [JsonProperty("unique_group_name")]
+
+ public string UniqueGroupName { get; set; }
+ }
+}
diff --git a/RedcapApi/Redcap.csproj b/RedcapApi/Redcap.csproj
index 07b9062..16d6ffc 100644
--- a/RedcapApi/Redcap.csproj
+++ b/RedcapApi/Redcap.csproj
@@ -1,7 +1,7 @@
- netstandard2.0
+ netstandard2.1
Michael Tran
Virginia Commonwealth University
True
@@ -10,51 +10,57 @@
This library allows applications on the .NET platform to make http calls to REDCap instances.
Redcap Api Library
RedcapAPI
- 1.0.9
- 1.0.9.0
+ 1.1.0
+ 1.1.0.0
redcap api library vcu
- - add improvements to 'ImportRecordsAsync'
- Add ability to declare csv delimiter to match REDCap's API paramters
-- minor update documentation
-- Minor version bump
-
+ New APIs have been added!
+-ExportLoggingAsync
+-ExportDagsAsync
+-ImportDagsAsync
+-DeleteDagsAsync
+-ExportUserDagAssignmentAsync
+-ImportUserDagAssignmentAsync
+-Added models to support new APIs
+Minor bugs fixes to existing APIs regarding optional parameters.
false
Library
en
- 1.0.9.0
+ 1.1.0.0
https://github.com/cctrbic/redcap-api/blob/master/LICENSE.md
https://github.com/cctrbic/redcap-api/blob/master/LICENSE.md
https://vortex.cctr.vcu.edu/images/ram_crest_160.png
MIT
- ram_crest_160.png
+ vcu.png
false
- bin\Debug\netcoreapp2.0\Redcap.xml
+
portable
true
AnyCPU
- D:\Github\redcap-api\RedcapApi\Redcap.xml
+
-
+
True
-
-
-
+
+
+
+
+
-
+
diff --git a/RedcapApi/Redcap.xml b/RedcapApi/Redcap.xml
index e24d829..c2cd359 100644
--- a/RedcapApi/Redcap.xml
+++ b/RedcapApi/Redcap.xml
@@ -3626,6 +3626,26 @@
+
+
+ Simplified demographics instrument that we can test with.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The format that the response object should be when returned from the http request.
diff --git a/RedcapApi/Services/ApiService.cs b/RedcapApi/Services/ApiService.cs
new file mode 100644
index 0000000..be3cb17
--- /dev/null
+++ b/RedcapApi/Services/ApiService.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading.Tasks;
+
+using Redcap.Broker;
+
+namespace Redcap.Services
+{
+ public class ApiService : IApiService
+ {
+ private readonly IApiBroker apiBroker;
+ public ApiService(IApiBroker apiBroker)
+ {
+ this.apiBroker = apiBroker;
+ }
+ ///
+ /// Exports a single record from REDCap.
+ ///
+ ///
+ ///
+ public async Task ExportRecordAsync()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/RedcapApi/Services/IApiService.cs b/RedcapApi/Services/IApiService.cs
new file mode 100644
index 0000000..c66e6e3
--- /dev/null
+++ b/RedcapApi/Services/IApiService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Redcap.Services
+{
+ public interface IApiService
+ {
+ Task ExportRecordAsync();
+ }
+}
\ No newline at end of file
diff --git a/RedcapApiDemo/Program.cs b/RedcapApiDemo/Program.cs
index 0484ae1..17eec2f 100644
--- a/RedcapApiDemo/Program.cs
+++ b/RedcapApiDemo/Program.cs
@@ -1,12 +1,23 @@
-using Newtonsoft.Json;
-using Redcap;
-using Redcap.Models;
-using System;
+using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
using System.Linq;
+using System.Reflection;
+
+using Newtonsoft.Json;
+
+using Redcap;
+using Redcap.Models;
+
+using Tynamix.ObjectFiller;
namespace RedcapApiDemo
{
+ ///
+ /// A class that represents the demopgrahics form for the demographics template.
+ /// Add additional properties that you've added to the redcap instrument as needed.
+ ///
public class Demographic
{
[JsonRequired]
@@ -20,6 +31,12 @@ public class Demographic
public string LastName { get; set; }
[JsonProperty("bio")]
public string Bio { get; set; }
+
+ ///
+ /// Test file uploads
+ ///
+ [JsonProperty("upload_file")]
+ public string UploadFile { get; set; }
}
class Program
{
@@ -188,8 +205,6 @@ public static void Main(string[] args)
//string importFileName = "test.txt";
//var pathExport = "C:\\redcap_download_files";
//var record = "1";
- var fieldName = "protocol_upload";
- var eventName = "event_1_arm_1";
//var repeatingInstrument = "1";
//Console.WriteLine("Calling ImportFile() . . .");
@@ -224,12 +239,12 @@ static void InitializeDemo()
* Change this token for your demo project
* Using one created from a local dev instance
*/
- string _superToken = "92F719F0EC97783D06B0E0FF49DC42DDA2247BFDC6759F324EE0D710FCA87C33";
+ string _superToken = "2E59CA118ABC17D393722524C501CF0BAC51689746E24BFDAF47B38798BD827A";
/*
* Using a local redcap development instsance
*/
string _uri = string.Empty;
- var fieldName = "protocol_upload";
+ var fieldName = "file_upload";
var eventName = "event_1_arm_1";
/*
@@ -239,10 +254,24 @@ static void InitializeDemo()
Console.WriteLine("Please make sure you include a working redcap api token.");
Console.WriteLine("Enter your redcap instance uri, example: http://localhost/redcap");
_uri = Console.ReadLine();
+ if (string.IsNullOrEmpty(_uri))
+ {
+ // provide a default one here..
+ _uri = "http://localhost/redcap";
+ }
_uri = _uri + "/api/";
Console.WriteLine("Enter your api token for the project to test: ");
var token = Console.ReadLine();
- _token = token;
+
+ if (string.IsNullOrEmpty(token))
+ {
+ _token = "DF70F2EC94AE05021F66423B386095BD";
+ }
+ else
+ {
+ _token = token;
+ }
+ Console.WriteLine($"Using Endpoint=> {_uri} Token => {_token}");
Console.WriteLine("-----------------------------Starting API Version 1.0.5+-------------");
Console.WriteLine("Starting demo for API Version 1.0.0+");
@@ -250,67 +279,112 @@ static void InitializeDemo()
Console.ReadLine();
Console.WriteLine("Creating a new instance of RedcapApi");
- var redcap_api_1_0_7 = new RedcapApi(_uri);
+ var redcap_api_1_1_0 = new RedcapApi(_uri);
Console.WriteLine($"Using {_uri.ToString()} for redcap api endpoint.");
+ #region ExportLoggingAsync()
+ Console.WriteLine("Calling ExportLoggingAsync() . . .");
+ Console.WriteLine($"Exporting logs for User . . .");
+ var ExportLoggingAsync = redcap_api_1_1_0.ExportLoggingAsync(_token, Content.Log, ReturnFormat.json, LogType.User).Result;
+ Console.WriteLine($"ExportLoggingAsync Results: {JsonConvert.DeserializeObject(ExportLoggingAsync)}");
+ Console.WriteLine("----------------------------Press Enter to Continue-------------");
+ Console.ReadLine();
+
+ #endregion
+
+ #region ImportDagsAsync()
+ Console.WriteLine("Calling ImportDagsAsync() . . .");
+ Console.WriteLine($"Importing Dags . . .");
+ var dags = CreateDags(5);
+ var ImportDagsAsyncResult = redcap_api_1_1_0.ImportDagsAsync(_token, Content.Dag, RedcapAction.Import, ReturnFormat.json, dags).Result;
+ Console.WriteLine($"ImportDagsAsync Results: {JsonConvert.DeserializeObject(ImportDagsAsyncResult)}");
+ Console.WriteLine("----------------------------Press Enter to Continue-------------");
+ Console.ReadLine();
+
+ #endregion
+
+ #region ExportDagsAsync()
+ Console.WriteLine("Calling ExportDagsAsync() . . .");
+ Console.WriteLine($"Exporting Dags . . .");
+ var ExportDagsAsyncResult = redcap_api_1_1_0.ExportDagsAsync(_token, Content.Dag, ReturnFormat.json).Result;
+ Console.WriteLine($"ExportDagsAsync Results: {JsonConvert.DeserializeObject(ExportDagsAsyncResult)}");
+ Console.WriteLine("----------------------------Press Enter to Continue-------------");
+ Console.ReadLine();
+ #endregion
+
+ #region DeleteDagsAsync()
+ Console.WriteLine("Calling DeleteDagsAsync() . . .");
+ Console.WriteLine($"Deleting Dags . . .");
+ var dagsToDelete = JsonConvert.DeserializeObject>(ExportDagsAsyncResult).Select(x => x.UniqueGroupName).ToArray();
+ var DeleteDagsAsyncResult = redcap_api_1_1_0.DeleteDagsAsync(_token, Content.Dag, RedcapAction.Delete, dagsToDelete).Result;
+ Console.WriteLine($"DeleteDagsAsync Results: {JsonConvert.DeserializeObject(DeleteDagsAsyncResult)}");
+ Console.WriteLine("----------------------------Press Enter to Continue-------------");
+ Console.ReadLine();
+ #endregion
+
#region ImportRecordsAsync()
Console.WriteLine("Calling ImportRecordsAsync() . . .");
- /*
- * Create a list of object of type instrument or fields. Add its properties then add it to the list.
- * record_id is required
- */
- var data = new List {
- new Demographic {
- FirstName = "Jon", LastName = "Doe", RecordId = "1"
- }
- };
- Console.WriteLine($"Importing record {string.Join(",", data.Select(x => x.RecordId).ToList())} . . .");
- var ImportRecordsAsync = redcap_api_1_0_7.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.tab, ReturnContent.count, OnErrorFormat.json).Result;
+ // get demographics data
+ var importDemographicsData = CreateDemographics(includeBio: true, 5);
+ Console.WriteLine("Serializing the data . . .");
+ Console.WriteLine($"Importing record {string.Join(",", importDemographicsData.Select(x => x.RecordId).ToList())} . . .");
+ var ImportRecordsAsync = redcap_api_1_1_0.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, importDemographicsData, "MDY", CsvDelimiter.tab, ReturnContent.count, OnErrorFormat.json).Result;
var ImportRecordsAsyncData = JsonConvert.DeserializeObject(ImportRecordsAsync);
Console.WriteLine($"ImportRecordsAsync Result: {ImportRecordsAsyncData}");
+ Console.WriteLine("----------------------------Press Enter to Continue-------------");
+ Console.ReadLine();
#endregion ImportRecordsAsync()
+ #region ExportRecordsAsync()
+ Console.WriteLine($"Calling ExportRecordsAsync()");
+ Console.WriteLine($"Using records from the imported method..");
+ var recordsToExport = importDemographicsData.Select(x => x.RecordId).ToArray();
+ var instrumentName = new string[] { "demographics" };
+ var ExportRecordsAsyncResult = redcap_api_1_1_0.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, recordsToExport, null, instrumentName).Result;
+ Console.WriteLine($"ExportRecordsAsyncResult: {ExportRecordsAsyncResult}");
Console.WriteLine("----------------------------Press Enter to Continue-------------");
Console.ReadLine();
+ #endregion ExportRecordsAsync()
#region DeleteRecordsAsync()
Console.WriteLine("Calling DeleteRecordsAsync() . . .");
- var records = new string[] { "1" };
- Console.WriteLine($"Deleting record {string.Join(",", records)} . . .");
- var DeleteRecordsAsync = redcap_api_1_0_7.DeleteRecordsAsync(_token, Content.Record, RedcapAction.Delete, records, 1).Result;
+ var records = importDemographicsData.Select(x => x.RecordId).ToArray();
+ Console.WriteLine($"Deleting record {string.Join(",", recordsToExport)} . . .");
+ var DeleteRecordsAsync = redcap_api_1_1_0.DeleteRecordsAsync(_token, Content.Record, RedcapAction.Delete, recordsToExport, 1).Result;
+ var DeleteRecordsAsyncData = JsonConvert.DeserializeObject(DeleteRecordsAsync);
+ Console.WriteLine($"DeleteRecordsAsync Result: {DeleteRecordsAsyncData}");
Console.WriteLine("----------------------------Press Enter to Continue-------------");
Console.ReadLine();
-
- Console.WriteLine($"DeleteRecordsAsync Result: {DeleteRecordsAsync}");
#endregion DeleteRecordsAsync()
+
#region ExportArmsAsync()
var arms = new string[] { };
Console.WriteLine("Calling ExportArmsAsync()");
- var ExportArmsAsyncResult = redcap_api_1_0_7.ExportArmsAsync(_token, Content.Arm, ReturnFormat.json, arms, OnErrorFormat.json).Result;
- Console.WriteLine($"ExportArmsAsyncResult: {ExportArmsAsyncResult}");
+ var ExportArmsAsyncResult = redcap_api_1_1_0.ExportArmsAsync(_token, Content.Arm, ReturnFormat.json, arms, OnErrorFormat.json).Result;
+ Console.WriteLine($"ExportArmsAsyncResult: {JsonConvert.DeserializeObject(ExportArmsAsyncResult)}");
#endregion ExportArmsAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
Console.ReadLine();
#region ImportArmsAsync()
- var ImportArmsAsyncData = new List { new RedcapArm { ArmNumber = "1", Name = "hooo" }, new RedcapArm { ArmNumber = "2", Name = "heee" }, new RedcapArm { ArmNumber = "3", Name = "hawww" } };
+ var ImportArmsAsyncData = CreateArms(count: 3);
Console.WriteLine("Calling ImportArmsAsync()");
- var ImportArmsAsyncResult = redcap_api_1_0_7.ImportArmsAsync(_token, Content.Arm, Override.False, RedcapAction.Import, ReturnFormat.json, ImportArmsAsyncData, OnErrorFormat.json).Result;
- Console.WriteLine($"ImportArmsAsyncResult: {ImportArmsAsyncResult}");
+ var ImportArmsAsyncResult = redcap_api_1_1_0.ImportArmsAsync(_token, Content.Arm, Override.False, RedcapAction.Import, ReturnFormat.json, ImportArmsAsyncData, OnErrorFormat.json).Result;
+ Console.WriteLine($"ImportArmsAsyncResult: {JsonConvert.DeserializeObject(ImportArmsAsyncResult)}");
#endregion ImportArmsAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
Console.ReadLine();
#region DeleteArmsAsync()
- var DeleteArmsAsyncData = new string[] { "3" };
+ var DeleteArmsAsyncData = ImportArmsAsyncData.Select(x => x.ArmNumber).ToArray();
Console.WriteLine("Calling DeleteArmsAsync()");
- var DeleteArmsAsyncResult = redcap_api_1_0_7.DeleteArmsAsync(_token, Content.Arm, RedcapAction.Delete, DeleteArmsAsyncData).Result;
- Console.WriteLine($"DeleteArmsAsyncResult: {DeleteArmsAsyncResult}");
+ var DeleteArmsAsyncResult = redcap_api_1_1_0.DeleteArmsAsync(_token, Content.Arm, RedcapAction.Delete, DeleteArmsAsyncData).Result;
+ Console.WriteLine($"DeleteArmsAsyncResult: {JsonConvert.DeserializeObject(DeleteArmsAsyncResult)}");
#endregion DeleteArmsAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
@@ -319,8 +393,8 @@ static void InitializeDemo()
#region ExportEventsAsync()
var ExportEventsAsyncData = new string[] { "1" };
Console.WriteLine("Calling ExportEventsAsync()");
- var ExportEventsAsyncResult = redcap_api_1_0_7.ExportEventsAsync(_token, Content.Event, ReturnFormat.json, ExportEventsAsyncData, OnErrorFormat.json).Result;
- Console.WriteLine($"ExportEventsAsyncResult: {ExportEventsAsyncResult}");
+ var ExportEventsAsyncResult = redcap_api_1_1_0.ExportEventsAsync(_token, Content.Event, ReturnFormat.json, ExportEventsAsyncData, OnErrorFormat.json).Result;
+ Console.WriteLine($"ExportEventsAsyncResult: {JsonConvert.DeserializeObject(ExportEventsAsyncResult)}");
#endregion ExportEventsAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
@@ -348,7 +422,7 @@ static void InitializeDemo()
CustomEventLabel = "hello clinical"
}
};
- var ImportEventsAsyncResult = redcap_api_1_0_7.ImportEventsAsync(_token, Content.Event, RedcapAction.Import, Override.False, ReturnFormat.json, eventList, OnErrorFormat.json).Result;
+ var ImportEventsAsyncResult = redcap_api_1_1_0.ImportEventsAsync(_token, Content.Event, RedcapAction.Import, Override.False, ReturnFormat.json, eventList, OnErrorFormat.json).Result;
Console.WriteLine($"ImportEventsAsyncResult: {ImportEventsAsyncResult}");
#endregion ImportEventsAsync()
@@ -359,7 +433,7 @@ static void InitializeDemo()
#region DeleteEventsAsync()
var DeleteEventsAsyncData = new string[] { "baseline_arm_1" };
Console.WriteLine("Calling DeleteEventsAsync()");
- var DeleteEventsAsyncResult = redcap_api_1_0_7.DeleteEventsAsync(_token, Content.Event, RedcapAction.Delete, DeleteEventsAsyncData).Result;
+ var DeleteEventsAsyncResult = redcap_api_1_1_0.DeleteEventsAsync(_token, Content.Event, RedcapAction.Delete, DeleteEventsAsyncData).Result;
Console.WriteLine($"DeleteEventsAsyncResult: {DeleteEventsAsyncResult}");
#endregion DeleteEventsAsync()
@@ -369,7 +443,7 @@ static void InitializeDemo()
#region ExportFieldNamesAsync()
Console.WriteLine("Calling ExportFieldNamesAsync(), first_name");
- var ExportFieldNamesAsyncResult = redcap_api_1_0_7.ExportFieldNamesAsync(_token, Content.ExportFieldNames, ReturnFormat.json, "first_name", OnErrorFormat.json).Result;
+ var ExportFieldNamesAsyncResult = redcap_api_1_1_0.ExportFieldNamesAsync(_token, Content.ExportFieldNames, ReturnFormat.json, "first_name", OnErrorFormat.json).Result;
Console.WriteLine($"ExportFieldNamesAsyncResult: {ExportFieldNamesAsyncResult}");
#endregion ExportFieldNamesAsync()
@@ -379,10 +453,13 @@ static void InitializeDemo()
#region ImportFileAsync()
var recordId = "1";
- var fileName = "test.txt";
- var fileUploadPath = @"C:\redcap_upload_files";
+ var fileName = "Demographics_TestProject_DataDictionary.csv";
+ DirectoryInfo myDirectory = new DirectoryInfo(Environment.CurrentDirectory);
+ string parentDirectory = myDirectory.Parent.FullName;
+ var parent = Directory.GetParent(parentDirectory).FullName;
+ var filePath = Directory.GetParent(parent).FullName + @"\Docs\";
Console.WriteLine($"Calling ImportFileAsync(), {fileName}");
- var ImportFileAsyncResult = redcap_api_1_0_7.ImportFileAsync(_token, Content.File, RedcapAction.Import, recordId, fieldName, eventName, null, fileName, fileUploadPath, OnErrorFormat.json).Result;
+ var ImportFileAsyncResult = redcap_api_1_1_0.ImportFileAsync(_token, Content.File, RedcapAction.Import, recordId, fieldName, eventName, null, fileName, filePath, OnErrorFormat.json).Result;
Console.WriteLine($"ImportFileAsyncResult: {ImportFileAsyncResult}");
#endregion ImportFileAsync()
@@ -392,7 +469,7 @@ static void InitializeDemo()
#region ExportFileAsync()
Console.WriteLine($"Calling ExportFileAsync(), {fileName} for field name {fieldName}, not save the file.");
- var ExportFileAsyncResult = redcap_api_1_0_7.ExportFileAsync(_token, Content.File, RedcapAction.Export, recordId, fieldName, eventName, null, OnErrorFormat.json).Result;
+ var ExportFileAsyncResult = redcap_api_1_1_0.ExportFileAsync(_token, Content.File, RedcapAction.Export, recordId, fieldName, eventName, null, OnErrorFormat.json).Result;
Console.WriteLine($"ExportFileAsyncResult: {ExportFileAsyncResult}");
#endregion ExportFileAsync()
@@ -402,7 +479,7 @@ static void InitializeDemo()
#region ExportFileAsync()
var filedDownloadPath = @"C:\redcap_download_files";
Console.WriteLine($"Calling ExportFileAsync(), {fileName} for field name {fieldName}, saving the file.");
- var ExportFileAsyncResult2 = redcap_api_1_0_7.ExportFileAsync(_token, Content.File, RedcapAction.Export, recordId, fieldName, eventName, null, OnErrorFormat.json, filedDownloadPath).Result;
+ var ExportFileAsyncResult2 = redcap_api_1_1_0.ExportFileAsync(_token, Content.File, RedcapAction.Export, recordId, fieldName, eventName, null, OnErrorFormat.json, filedDownloadPath).Result;
Console.WriteLine($"ExportFileAsyncResult2: {ExportFileAsyncResult2}");
#endregion ExportFileAsync()
@@ -411,7 +488,7 @@ static void InitializeDemo()
#region DeleteFileAsync()
Console.WriteLine($"Calling DeleteFileAsync(), deleting file: {fileName} for field: {fieldName}");
- var DeleteFileAsyncResult = redcap_api_1_0_7.DeleteFileAsync(_token, Content.File, RedcapAction.Delete, recordId, fieldName, eventName, "1", OnErrorFormat.json).Result;
+ var DeleteFileAsyncResult = redcap_api_1_1_0.DeleteFileAsync(_token, Content.File, RedcapAction.Delete, recordId, fieldName, eventName, "1", OnErrorFormat.json).Result;
Console.WriteLine($"DeleteFileAsyncResult: {DeleteFileAsyncResult}");
#endregion DeleteFileAsync()
@@ -420,7 +497,7 @@ static void InitializeDemo()
#region ExportInstrumentsAsync()
Console.WriteLine($"Calling DeleteFileAsync()");
- var ExportInstrumentsAsyncResult = redcap_api_1_0_7.ExportInstrumentsAsync(_token, Content.Instrument, ReturnFormat.json).Result;
+ var ExportInstrumentsAsyncResult = redcap_api_1_1_0.ExportInstrumentsAsync(_token, Content.Instrument, ReturnFormat.json).Result;
Console.WriteLine($"ExportInstrumentsAsyncResult: {ExportInstrumentsAsyncResult}");
#endregion ExportInstrumentsAsync()
@@ -429,7 +506,7 @@ static void InitializeDemo()
#region ExportPDFInstrumentsAsync()
Console.WriteLine($"Calling ExportPDFInstrumentsAsync(), returns raw");
- var ExportPDFInstrumentsAsyncResult = redcap_api_1_0_7.ExportPDFInstrumentsAsync(_token, Content.Pdf, recordId, eventName, "demographics", true, OnErrorFormat.json).Result;
+ var ExportPDFInstrumentsAsyncResult = redcap_api_1_1_0.ExportPDFInstrumentsAsync(_token, Content.Pdf, recordId, eventName, "demographics", true, OnErrorFormat.json).Result;
Console.WriteLine($"ExportInstrumentsAsyncResult: {JsonConvert.SerializeObject(ExportPDFInstrumentsAsyncResult)}");
#endregion ExportPDFInstrumentsAsync()
@@ -438,18 +515,19 @@ static void InitializeDemo()
#region ExportPDFInstrumentsAsync()
Console.WriteLine($"Calling ExportPDFInstrumentsAsync(), saving pdf file to {filedDownloadPath}");
- var ExportPDFInstrumentsAsyncResult2 = redcap_api_1_0_7.ExportPDFInstrumentsAsync(_token, recordId, eventName, "demographics", true, filedDownloadPath, OnErrorFormat.json).Result;
+ var ExportPDFInstrumentsAsyncResult2 = redcap_api_1_1_0.ExportPDFInstrumentsAsync(_token, recordId, eventName, "demographics", true, filedDownloadPath, OnErrorFormat.json).Result;
Console.WriteLine($"ExportPDFInstrumentsAsyncResult2: {ExportPDFInstrumentsAsyncResult2}");
#endregion ExportPDFInstrumentsAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
Console.ReadLine();
- #region ExportInstrumentMappingAsync()
- Console.WriteLine($"Calling ExportInstrumentMappingAsync()");
- var ExportInstrumentMappingAsyncResult = redcap_api_1_0_7.ExportInstrumentMappingAsync(_token, Content.FormEventMapping, ReturnFormat.json, arms, OnErrorFormat.json).Result;
- Console.WriteLine($"ExportInstrumentMappingAsyncResult: {ExportInstrumentMappingAsyncResult}");
- #endregion ExportInstrumentMappingAsync()
+ //#region ExportInstrumentMappingAsync()
+ //Console.WriteLine($"Calling ExportInstrumentMappingAsync()");
+ //var armsToExportInstrumentMapping = arms;
+ //var ExportInstrumentMappingAsyncResult = redcap_api_1_1_0.ExportInstrumentMappingAsync(_token, Content.FormEventMapping, ReturnFormat.json, armsToExportInstrumentMapping, OnErrorFormat.json).Result;
+ //Console.WriteLine($"ExportInstrumentMappingAsyncResult: {ExportInstrumentMappingAsyncResult}");
+ //#endregion ExportInstrumentMappingAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
Console.ReadLine();
@@ -457,7 +535,7 @@ static void InitializeDemo()
#region ImportInstrumentMappingAsync()
var importInstrumentMappingData = new List { new FormEventMapping { arm_num = "1", unique_event_name = "clinical_arm_1", form = "demographics" } };
Console.WriteLine($"Calling ImportInstrumentMappingAsync()");
- var ImportInstrumentMappingAsyncResult = redcap_api_1_0_7.ImportInstrumentMappingAsync(_token, Content.FormEventMapping, ReturnFormat.json, importInstrumentMappingData, OnErrorFormat.json).Result;
+ var ImportInstrumentMappingAsyncResult = redcap_api_1_1_0.ImportInstrumentMappingAsync(_token, Content.FormEventMapping, ReturnFormat.json, importInstrumentMappingData, OnErrorFormat.json).Result;
Console.WriteLine($"ImportInstrumentMappingAsyncResult: {ImportInstrumentMappingAsyncResult}");
#endregion ImportInstrumentMappingAsync()
@@ -466,7 +544,7 @@ static void InitializeDemo()
#region ExportMetaDataAsync()
Console.WriteLine($"Calling ExportMetaDataAsync()");
- var ExportMetaDataAsyncResult = redcap_api_1_0_7.ExportMetaDataAsync(_token, Content.MetaData, ReturnFormat.json, null, null, OnErrorFormat.json).Result;
+ var ExportMetaDataAsyncResult = redcap_api_1_1_0.ExportMetaDataAsync(_token, Content.MetaData, ReturnFormat.json, null, null, OnErrorFormat.json).Result;
Console.WriteLine($"ExportMetaDataAsyncResult: {ExportMetaDataAsyncResult}");
#endregion ExportMetaDataAsync()
@@ -490,7 +568,7 @@ static void InitializeDemo()
var projectData = new List { new RedcapProject { project_title = "Amazing Project ", purpose = ProjectPurpose.Other, purpose_other = "Test" } };
Console.WriteLine($"Calling CreateProjectAsync(), creating a new project with Amazing Project as title, purpose 1 (other) ");
Console.WriteLine($"-----------------------Notice the use of SUPER TOKEN------------------------");
- var CreateProjectAsyncResult = redcap_api_1_0_7.CreateProjectAsync(_superToken, Content.Project, ReturnFormat.json, projectData, OnErrorFormat.json, null).Result;
+ var CreateProjectAsyncResult = redcap_api_1_1_0.CreateProjectAsync(_superToken, Content.Project, ReturnFormat.json, projectData, OnErrorFormat.json, null).Result;
Console.WriteLine($"CreateProjectAsyncResult: {CreateProjectAsyncResult}");
#endregion CreateProjectAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
@@ -499,7 +577,7 @@ static void InitializeDemo()
#region ImportProjectInfoAsync()
var projectInfo = new RedcapProjectInfo { ProjectTitle = "Updated Amazing Project ", Purpose = ProjectPurpose.QualityImprovement, SurveysEnabled = 1 };
Console.WriteLine($"Calling ImportProjectInfoAsync()");
- var ImportProjectInfoAsyncResult = redcap_api_1_0_7.ImportProjectInfoAsync(_token, Content.ProjectSettings, ReturnFormat.json, projectInfo).Result;
+ var ImportProjectInfoAsyncResult = redcap_api_1_1_0.ImportProjectInfoAsync(_token, Content.ProjectSettings, ReturnFormat.json, projectInfo).Result;
Console.WriteLine($"ImportProjectInfoAsyncResult: {ImportProjectInfoAsyncResult}");
#endregion ImportProjectInfoAsync()
Console.WriteLine("----------------------------Press Enter to Continue-------------");
@@ -507,24 +585,15 @@ static void InitializeDemo()
#region ExportProjectInfoAsync()
Console.WriteLine($"Calling ExportProjectInfoAsync()");
- var ExportProjectInfoAsyncResult = redcap_api_1_0_7.ExportProjectInfoAsync(_token, Content.ProjectSettings, ReturnFormat.json).Result;
+ var ExportProjectInfoAsyncResult = redcap_api_1_1_0.ExportProjectInfoAsync(_token, Content.ProjectSettings, ReturnFormat.json).Result;
Console.WriteLine($"ExportProjectInfoAsyncResult: {ExportProjectInfoAsyncResult}");
#endregion ExportProjectInfoAsync()
Console.WriteLine("----------------------------Demo completed! Press Enter to Exit-------------");
Console.ReadLine();
- #region ExportRecordsAsync()
- Console.WriteLine($"Calling ExportRecordsAsync()");
- Console.WriteLine($"Using record 1");
- Console.WriteLine($"Using instrumentname = demographics");
- var instrumentName = new string[] { "demographics" };
- var ExportRecordsAsyncResult = redcap_api_1_0_7.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, null, null, instrumentName).Result;
- Console.WriteLine($"ExportRecordsAsyncResult: {ExportProjectInfoAsyncResult}");
- #endregion ExportRecordsAsync()
- Console.WriteLine("----------------------------Demo completed! Press Enter to Exit-------------");
- Console.ReadLine();
+
}
public static Demographic GetRandomPerson(string id, bool includeBio = false)
@@ -539,6 +608,49 @@ public static Demographic GetRandomPerson(string id, bool includeBio = false)
}
return person;
}
+ public static List CreateDemographics(bool includeBio = false, int count = 1)
+ {
+ var demographics = new List();
+ for (var i = 1; i <= count; i++)
+ {
+ var _demographicFiller = new Filler();
+
+ _demographicFiller.Setup().OnProperty(x => x.RecordId).Use(i.ToString());
+ var _demographic = _demographicFiller.Create();
+ if (includeBio)
+ {
+ _demographic.Bio = VeryLargeText;
+ }
+ demographics.Add(_demographic);
+ }
+
+ return demographics;
+ }
+ public static List CreateArms(int count = 1)
+ {
+
+ var arms = new List();
+ for (var i = 0; i < count; i++)
+ {
+ var _demographicFiller = new Filler();
+ _demographicFiller.Setup().OnProperty(x => x.ArmNumber).Use(i.ToString());
+ var _demographic = _demographicFiller.Create();
+ arms.Add(_demographic);
+ }
+ return arms;
+ }
+ public static List CreateDags(int count = 1){
+
+ var dags = new List();
+ for(var i = 0; i < count; i++)
+ {
+ var _dagFiller = new Filler();
+ _dagFiller.Setup().OnProperty(x => x.UniqueGroupName).Use(string.Empty);
+ var _dag = _dagFiller.Create();
+ dags.Add(_dag);
+ }
+ return dags;
+ }
}
diff --git a/RedcapApiDemo/RedcapApiDemo.csproj b/RedcapApiDemo/RedcapApiDemo.csproj
index 2713689..5c7cc2a 100644
--- a/RedcapApiDemo/RedcapApiDemo.csproj
+++ b/RedcapApiDemo/RedcapApiDemo.csproj
@@ -2,13 +2,14 @@
Exe
- netcoreapp2.0
+ net5.0
-
-
-
+
+
+
+
diff --git a/RedcapApiDemo/SampleData.cs b/RedcapApiDemo/SampleData.cs
deleted file mode 100644
index a6bc46a..0000000
--- a/RedcapApiDemo/SampleData.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace RedcapApiDemo
-{
- class SampleData
- {
- }
-}
diff --git a/Tests/RedcapApiTests.DataAccessGroups.cs b/Tests/RedcapApiTests.DataAccessGroups.cs
new file mode 100644
index 0000000..3b9ea28
--- /dev/null
+++ b/Tests/RedcapApiTests.DataAccessGroups.cs
@@ -0,0 +1,23 @@
+/*
+ * REDCap API Library
+ * Michael Tran tranpl@vcu.edu, tranpl@outlook.com
+ * Biomedical Informatics Core
+ * C. Kenneth and Dianne Wright Center for Clinical and Translational Research
+ * Virginia Commonwealth University
+ *
+ * Copyright (c) 2021 Virginia Commonwealth University
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+using Redcap;
+using Redcap.Interfaces;
+
+namespace Tests
+{
+ public partial class RedcapApiTests
+ {
+ }
+}
diff --git a/Tests/RedcapApiTests.Records.cs b/Tests/RedcapApiTests.Records.cs
new file mode 100644
index 0000000..00eec8f
--- /dev/null
+++ b/Tests/RedcapApiTests.Records.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using FluentAssertions;
+
+using Moq;
+
+using Redcap.Broker;
+using Redcap.Models;
+using Redcap.Services;
+
+using RestSharp;
+
+using Xunit;
+
+namespace Tests
+{
+ public partial class RedcapApiTests
+ {
+ [Fact]
+ public async Task ShouldExportRecordAsync()
+ {
+ // arrange
+ Demographic demographicInstrument = CreateDemographicsInstrument();
+ Demographic inputDemographicInstrument = demographicInstrument;
+ Demographic retrievedDemographicInstrument = inputDemographicInstrument;
+ Demographic expectedDemographicInstrument = retrievedDemographicInstrument;
+ var request = new RestRequest(apiUri, Method.POST);
+ apiBrokerMock.Setup(broker => broker.ExecuteAsync(request))
+ .ReturnsAsync(retrievedDemographicInstrument);
+ // act
+ Demographic actualDemographicInstrument = await apiService.ExportRecordAsync();
+
+ // assert
+ actualDemographicInstrument.Should().BeEquivalentTo(expectedDemographicInstrument);
+ }
+ }
+}
diff --git a/Tests/RedcapApiTests.cs b/Tests/RedcapApiTests.cs
index a954ab7..88c8763 100644
--- a/Tests/RedcapApiTests.cs
+++ b/Tests/RedcapApiTests.cs
@@ -1,846 +1,37 @@
-using Newtonsoft.Json;
-using Redcap;
-using Redcap.Models;
-using Redcap.Utilities;
+using System;
using System.Collections.Generic;
using System.Linq;
-using Xunit;
+using System.Text;
+using System.Threading.Tasks;
-namespace Tests
-{
- ///
- /// Simplified demographics instrument that we can test with.
- ///
- public class Demographic
- {
- [JsonRequired]
- [JsonProperty("record_id")]
- public string RecordId { get; set; }
+using Moq;
- [JsonProperty("first_name")]
- public string FirstName { get; set; }
+using Redcap.Broker;
+using Redcap.Models;
+using Redcap.Services;
- [JsonProperty("last_name")]
- public string LastName { get; set; }
- }
- ///
- /// Very simplified test class for Redcap Api
- /// This is not a comprehensive test, add more if you'd like.
- /// Make sure you have some records in the redcap project for the instance you are testing
- ///
- public class RedcapApiTests
+using Tynamix.ObjectFiller;
+
+namespace Tests
+{
+ public partial class RedcapApiTests
{
- // API Token for a project in a local instance of redcap
- private const string _token = "A8E6949EF4380F1111C66D5374E1AE6C";
- // local instance of redcap api uri
- private const string _uri = "http://localhost/redcap/api/";
- private readonly RedcapApi _redcapApi;
+ private readonly IApiService apiService;
+ private readonly Mock apiBrokerMock;
+ private string apiUri = "http://localhost:8080/redcap";
public RedcapApiTests()
{
- _redcapApi = new RedcapApi(_uri);
- }
-
- [Fact]
- public async void CanImportRecordsAsyncAsDictionary_ShouldReturn_CountString()
- {
- // Arrange
- var data = new List> { };
- var keyValues = new Dictionary { };
- keyValues.Add("first_name", "Jon");
- keyValues.Add("last_name", "Doe");
- keyValues.Add("record_id", "8");
- keyValues.Add("redcap_repeat_instrument", "demographics");
- data.Add(keyValues);
- // Act
- var result = await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
-
- // Assert
- // Expecting a string of 1 since we are importing one record
- Assert.Contains("1", result);
- }
- [Fact, TestPriority(0)]
- public void CanImportRecordsAsync_ShouldReturn_CountString()
- {
- // Arrange
- var data = new List { new Demographic { FirstName = "Jon", LastName = "Doe", RecordId = "1" } };
- // Act
- /*
- * Using API Version 1.0.0+
- */
- // executing method using default options
- var result = _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json).Result;
-
- var res = JsonConvert.DeserializeObject(result).ToString();
-
- // Assert
- // Expecting a string of 1 since we are importing one record
- Assert.Contains("1", res);
- }
- [Fact]
- public async void CanDeleteRecordsAsync_ShouldReturn_string()
- {
- // Arrange
- var record = new List> { };
- var keyValues = new Dictionary { };
- keyValues.Add("first_name", "Jon");
- keyValues.Add("last_name", "Doe");
- keyValues.Add("record_id", "8");
- record.Add(keyValues);
- // import records so we can delete it for this test
- await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, true, record, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
-
- var records = new string[]
- {
- "8"
- };
- var arm = 1;
- // Act
- var result = await _redcapApi.DeleteRecordsAsync(_token, Content.Record, RedcapAction.Delete, records, arm);
-
- // Assert
- // Expecting a string of 1 since we are deleting one record
- Assert.Contains("1", result);
- }
- [Fact]
- public async void CanImportRepeatingInstrumentsAndEvents_ShouldReturn_string()
- {
- // exact instrument names in data dictionary to repeat
- var repeatingInstruments = new List {
- new RedcapRepeatInstrument {
- EventName = "event_1_arm_1",
- FormName = "demographics",
- CustomFormLabel = "TestTestTest"
- }
- };
- var result = await _redcapApi.ImportRepeatingInstrumentsAndEvents(_token, repeatingInstruments);
- // Expect "1" as we are importing a single repeating instrument
- Assert.Contains("1", result);
- }
- ///
- /// Test ability to export repeating instruments and events
- /// API Version 1.0.0+
- ///
- [Fact]
- public async void CanExportRepeatingInstrumentsAndEvents_ShouldReturn_string()
- {
- // Arrange
- // We'll importa a single repeating form so we can run test
- // By executing the import repeating instrument api, redcap will
- // enable the repeating instruments and events feature automatically
- var repeatingInstruments = new List {
- new RedcapRepeatInstrument {
- EventName = "event_1_arm_1",
- FormName = "demographics",
- CustomFormLabel = "TestTestTest"
- }
- };
- await _redcapApi.ImportRepeatingInstrumentsAndEvents(_token, repeatingInstruments);
-
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var result = await _redcapApi.ExportRepeatingInstrumentsAndEvents(_token);
-
- // Assert
- // Expecting event names, form name and custom form labels
- // we imported it above
- Assert.Contains("event_name", result);
- Assert.Contains("form_name", result);
- Assert.Contains("custom_form_label", result);
- Assert.Contains("demographics", result);
- Assert.Contains("event_1_arm_1", result);
- Assert.Contains("TestTestTest", result);
- }
-
- ///
- /// Can Export Arms
- /// All arms should be returned
- /// Using API version 1.0.0+
- ///
- [Fact, TestPriority(1)]
- public void CanExportArmsAsync_AllArms_ShouldContain_armnum()
- {
- // Arrange
-
-
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var result = _redcapApi.ExportArmsAsync(_token).Result;
- var data = JsonConvert.DeserializeObject(result).ToString();
-
- // Assert
- // Expecting multiple arms to be return since we asked for all arms by not providing any arms by passing null for the params
- // ** Important to notice is that if we didn't add any events to an arm, even if there are more, only
- // arms with events will be returned **
- Assert.Contains("1", data);
- Assert.Contains("2", data);
- }
-
-
- ///
- /// Can Import Arms
- /// Using API version 1.0.0+
- ///
- [Fact, TestPriority(0)]
- public async void CanImportArmsAsync_SingleArm_ShouldReturn_number()
- {
- // Arrange
- var armlist = new List
- {
- new RedcapArm{ArmNumber = "3", Name = "testarm3_this_will_be_deleted"},
- new RedcapArm{ArmNumber = "2", Name = "testarm2_this_will_be_deleted"},
- new RedcapArm{ArmNumber = "4", Name = "testarm4_this_will_be_deleted"},
- };
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var result = await _redcapApi.ImportArmsAsync(_token, Content.Arm, Override.False, RedcapAction.Import, ReturnFormat.json, armlist, OnErrorFormat.json);
-
- // Assert
- // Expecting "3", the number of arms imported, since we pass 3 arm to be imported
- Assert.Contains("3", result);
- }
- ///
- /// Can Delete Arms
- /// Using API version 1.0.0+
- ///
- [Fact, TestPriority(99)]
- public async void CanDeleteArmsAsync_SingleArm_ShouldReturn_number()
- {
- // Arrange
- // Initially if we enable a project as longitudinal, redcap will append a single arm.
- // We are adding a single arm(3) to the project.
- var redcapArms = new List {
- new RedcapArm { ArmNumber = "3", Name = "Arm 3" },
- };
-
- // Make sure we have an arm with a few events before trying to delete one.
- var importArmsResults = await _redcapApi.ImportArmsAsync(_token, Content.Arm, Override.True, RedcapAction.Import, ReturnFormat.json, redcapArms, OnErrorFormat.json);
-
- // arms(#) to be deleted
- var armarray = new string[]
- {
- "3"
- };
-
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var result = _redcapApi.DeleteArmsAsync(_token, Content.Arm, RedcapAction.Delete, armarray).Result;
- var data = JsonConvert.DeserializeObject(result).ToString();
-
- // Assert
- // Expecting "1", the number of arms deleted, since we pass 1 arm to be deleted
- // You'll need an arm 3 to be available first, run import arm
- Assert.Contains("1", data);
- }
- ///
- /// Can Export Events
- /// Using API version 1.0.0+
- ///
- [Fact]
- public void CanExportEventsAsync_SingleEvent_ShouldReturn_event()
- {
- // Arrange
-
- var ExportEventsAsyncData = new string[] { "1" };
-
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var result = _redcapApi.ExportEventsAsync(_token, Content.Event, ReturnFormat.json, ExportEventsAsyncData, OnErrorFormat.json).Result;
- var data = JsonConvert.DeserializeObject(result).ToString();
-
- // Assert
- Assert.Contains("event_name", data);
- }
- ///
- /// Can Import Events
- /// Using API version 1.0.0+
- ///
- [Fact, TestPriority(0)]
- public void CanImportEventsAsync_MultipleEvents_ShouldReturn_number()
- {
- // Arrange
-
- var apiEndpoint = _uri;
- var eventList = new List {
- new RedcapEvent {
- EventName = "Event 1",
- ArmNumber = "1",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "event_1_arm_1",
- CustomEventLabel = "Baseline"
- },
- new RedcapEvent {
- EventName = "Event 2",
- ArmNumber = "1",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "event_2_arm_1",
- CustomEventLabel = "First Visit"
- },
- new RedcapEvent {
- EventName = "Event 3",
- ArmNumber = "1",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "event_3_arm_1",
- CustomEventLabel = "Clinical"
- }
- };
-
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var _redcapApi = new RedcapApi(apiEndpoint);
- var result = _redcapApi.ImportEventsAsync(_token, Content.Event, RedcapAction.Import, Override.False, ReturnFormat.json, eventList, OnErrorFormat.json).Result;
- var data = JsonConvert.DeserializeObject(result).ToString();
-
- // Assert
- // Expecting "3", since we had 3 redcap events imported
- Assert.Contains("3", data);
- }
- ///
- /// Can delete Events
- /// Using API version 1.0.0+
- ///
- [Fact, TestPriority(10)]
- public async void CanDeleteEventsAsync_SingleEvent_ShouldReturn_number()
- {
- // Arrange
- var events = new List {
- new RedcapEvent {
- ArmNumber = "1",
- EventName = "Clinical Event 1",
- UniqueEventName = "clinical_arm_1"}
- };
- await _redcapApi.ImportEventsAsync(_token, Content.Event, RedcapAction.Import, Override.True, ReturnFormat.json, events);
- // the event above, redcap appends _arm_1 when you add an arm_#
- var DeleteEventsAsyncData = new string[] { "clinical_event_1_arm_1" };
-
- // Act
- /*
- * Using API Version 1.0.0+
- */
- var result = await _redcapApi.DeleteEventsAsync(_token, Content.Event, RedcapAction.Delete, DeleteEventsAsyncData);
-
- // Assert
- // Expecting "1", since we had 1 redcap events imported
- Assert.Contains("1", result);
- }
- [Fact]
- public void CanExportRecordAsync_SingleRecord_ShouldReturn_String_1()
- {
- // Arrange
- var recordId = "1";
- // Act
- /*
- * Just passing the required parameters
- */
- var result = _redcapApi.ExportRecordAsync(_token, Content.Record, recordId).Result;
-
- // Assert
- Assert.Contains("1", result);
-
- }
- ///
- /// Export / Get single record
- ///
- [Fact]
- public async void CanExportRecordsAsync_SingleRecord_FromRepeatingInstrument_ShouldContain_string_1()
- {
- // Arrange
- var record = new string[]
- {
- "1"
- };
- var redcapEvent = new string[] { "event_1_arm_1" };
- // Act
- var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, record, null, null, redcapEvent);
-
- // Assert
- Assert.Contains("1", result);
- }
- ///
- /// Can export multiple records
- ///
- [Fact]
- public async void CanExportRecordsAsync_MultipleRecord_ShouldContain_string_1_2()
- {
- // Arrange
- var records = new string[] { "1, 2" };
-
- // Act
- var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
-
- // Assert
- Assert.Contains("1", result);
- Assert.Contains("2", result);
- }
- ///
- /// Can export single record
- ///
- [Fact]
- public async void CanExportRecordAsync_SingleRecord_ShouldContain_string_1()
- {
- // Arrange
- var records = new string[] { "1" };
-
- // Act
- var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
-
- // Assert
- Assert.Contains("1", result);
- }
-
- ///
- /// Can export redcap version
- /// API Version 1.0.0+
- ///
- [Fact]
- public async void CanExportRedcapVersion_VersionNumber_Shouldontain_Number()
- {
- // Arrange
- // Assume current redcap version is 8+
- var currentRedcapVersion = "8";
-
- // Act
- var result = await _redcapApi.ExportRedcapVersionAsync(_token, Content.Version);
-
- // Assert
- // Any version 8.XX will do
- Assert.Contains(currentRedcapVersion, result);
-
- }
- ///
- /// Can export users
- /// API Version 1.0.0+
- ///
- [Fact]
- public async void CanExportUsers_AllUsers_ShouldReturn_username()
- {
- // Arrange
- var username = "tranpl";
-
- // Act
- var result = await _redcapApi.ExportUsersAsync(_token, ReturnFormat.json);
- // Assert
- Assert.Contains(username, result);
-
- }
-
- ///
- /// Can export records
- ///
- [Fact]
- public async void CanExportRecordsAsync_AllRecords_ShouldReturn_String()
- {
- // Arrange
-
- // Act
- var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat);
- var data = JsonConvert.DeserializeObject>(result);
-
- // Assert
- // expecting a list of
- Assert.True(data.Count > 1);
- }
- ///
- /// Can import meta data
- /// This method allows you to import metadata (i.e., Data Dictionary) into a project.
- /// Notice: Because of this method's destructive nature, it is only available for use for projects in Development status.
- /// API Version 1.0.0+
- ///
- [Fact]
- public async void CanImportMetaDataAsync_Metadata_ShouldReturn_string_record_id()
- {
- // Arrange
- // This will wipe out the current data dictionary and update with the below meta.
- var metata = new List {
- new RedcapMetaData{
- field_name ="record_id",
- form_name = "demographics",
- field_label ="Study Id",
- field_type ="text",
- section_header = "",
- },
- new RedcapMetaData{
- field_name ="first_name",
- form_name = "demographics",
- field_label ="First Name",
- field_type ="text",
- section_header = "Contact Information",
- identifier = "y"
- },
- new RedcapMetaData{
- field_name ="last_name",
- form_name = "demographics",
- field_label ="Last Name",
- field_type ="text",
- identifier = "y"
- },
- new RedcapMetaData{
- field_name ="address",
- form_name = "demographics",
- field_label ="Street, City, State, ZIP",
- field_type ="notes",
- identifier = "y"
- },
- new RedcapMetaData{
- field_name ="email",
- form_name = "demographics",
- field_label ="E-mail",
- field_type ="text",
- identifier = "y",
- text_validation_type_or_show_slider_number = "email"
- },
- new RedcapMetaData{
- field_name ="dob",
- form_name = "demographics",
- field_label ="Date of Birth",
- field_type ="text",
- identifier = "y",
- text_validation_type_or_show_slider_number = "date_ymd"
- },
- new RedcapMetaData{
- field_name ="file_upload",
- form_name = "demographics",
- field_label ="File Upload",
- field_type ="file"
- }
- };
- // Act
- var result = await _redcapApi.ImportMetaDataAsync(_token, Content.MetaData, ReturnFormat.json, metata);
-
- // Assert
- // Expecting 7 metada objects imported
- Assert.Contains("7", result);
+ apiBrokerMock = new Mock();
+ apiService = new ApiService(apiBroker: apiBrokerMock.Object);
}
- ///
- /// Can export meta data
- ///
- [Fact]
- public async void CanExportMetaDataAsync_Metadata_ShouldReturn_NumberMetadataFields()
+ private static Demographic CreateDemographicsInstrument()
{
- // This will wipe out the current data dictionary and update with the below meta.
- var metata = new List {
- new RedcapMetaData{
- field_name ="record_id",
- form_name = "demographics",
- field_label ="Study Id",
- field_type ="text",
- section_header = "",
- },
- new RedcapMetaData{
- field_name ="first_name",
- form_name = "demographics",
- field_label ="First Name",
- field_type ="text",
- section_header = "Contact Information",
- identifier = "y"
- },
- new RedcapMetaData{
- field_name ="last_name",
- form_name = "demographics",
- field_label ="Last Name",
- field_type ="text",
- identifier = "y"
- },
- new RedcapMetaData{
- field_name ="address",
- form_name = "demographics",
- field_label ="Street, City, State, ZIP",
- field_type ="notes",
- identifier = "y"
- },
- new RedcapMetaData{
- field_name ="email",
- form_name = "demographics",
- field_label ="E-mail",
- field_type ="text",
- identifier = "y",
- text_validation_type_or_show_slider_number = "email"
- },
- new RedcapMetaData{
- field_name ="dob",
- form_name = "demographics",
- field_label ="Date of Birth",
- field_type ="text",
- identifier = "y",
- text_validation_type_or_show_slider_number = "date_ymd"
- },
- new RedcapMetaData{
- field_name ="file_upload",
- form_name = "demographics",
- field_label ="File Upload",
- field_type ="file"
- }
- };
- // import 7 metadata fields into the project
- // this creates an instrument named demographics with 7 fields
- await _redcapApi.ImportMetaDataAsync(_token, Content.MetaData, ReturnFormat.json, metata);
-
- // Act
- var result = await _redcapApi.ExportMetaDataAsync(_token, ReturnFormat.json);
- var data = JsonConvert.DeserializeObject>(result);
- // Assert
- // expecting 7 metadata to be exported
- Assert.True(data.Count >= 7);
- }
- ///
- /// Can export arms
- ///
- [Fact]
- public async void CanExportArmsAsync_Arms_ShouldReturn_arms_array()
- {
- // Arrange
- // Importing 3 arms so that we can run the test to export
- var redcapArms = new List
- {
- new RedcapArm{ArmNumber = "3", Name = "testarm3_this_will_be_deleted"},
- new RedcapArm{ArmNumber = "2", Name = "testarm2_this_will_be_deleted"},
- new RedcapArm{ArmNumber = "4", Name = "testarm4_this_will_be_deleted"},
- };
- // Import Arms so we can test
- await _redcapApi.ImportArmsAsync(_token, Content.Arm, Override.False, RedcapAction.Import, ReturnFormat.json, redcapArms, OnErrorFormat.json);
- var listOfEvents = new List() {
- new RedcapEvent{
- ArmNumber = "2",
- CustomEventLabel = "HelloEvent1",
- EventName = "Import Event 1",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "import_event_1_arm_2"
- },
- new RedcapEvent{
- ArmNumber = "2",
- CustomEventLabel = "HelloEvent2",
- EventName = "Import Event 2",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "import_event_2_arm_2"
- },
- new RedcapEvent{
- ArmNumber = "2",
- CustomEventLabel = "HelloEvent3",
- EventName = "Import Event 3",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "import_event_3_arm_2"
- }
- };
- // Import Events so we can test
- await _redcapApi.ImportEventsAsync(_token, Override.False, ReturnFormat.json, listOfEvents, OnErrorFormat.json);
- // we want to export arm 1 and arm 2
- var exportArms = new string[] { "1", "2" };
- // Act
- var result = await _redcapApi.ExportArmsAsync(_token, Content.Arm, ReturnFormat.json, exportArms);
-
- // Assert
- // In order for the arms array to be returned, events for the specific arm
- // needs to be present. An arm without any events will not be returned.
- Assert.Contains("arm_num", result);
- Assert.Contains("1", result);
- Assert.Contains("2", result);
- }
- ///
- /// Can import arms
- ///
- [Fact, TestPriority(0)]
- public async void CanImportEventsAsync_Events_ShouldReturn_Number()
- {
- // Arrange
- var listOfEvents = new List() {
- new RedcapEvent{
- ArmNumber = "3",
- CustomEventLabel = "HelloEvent1",
- EventName = "Import Event 1",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "import_event_1_arm_3"
- },
- new RedcapEvent{
- ArmNumber = "3",
- CustomEventLabel = "HelloEvent2",
- EventName = "Import Event 2",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "import_event_2_arm_3"
- },
- new RedcapEvent{
- ArmNumber = "3",
- CustomEventLabel = "HelloEvent3",
- EventName = "Import Event 3",
- DayOffset = "1",
- MinimumOffset = "0",
- MaximumOffset = "0",
- UniqueEventName = "import_event_3_arm_3"
- }
- };
-
- // Act
- var result = await _redcapApi.ImportEventsAsync(_token, Override.False, ReturnFormat.json, listOfEvents, OnErrorFormat.json);
-
- // Assert
- // Expecting 3 since we imported 3 events
- Assert.Contains("3", result);
- }
- ///
- /// Test attempts to import a file into the redcap project
- /// There are a few assumptions, please make sure you have the files and folders
- /// exactly as shown, or name it to your needs.
- /// API Version 1.0.0+
- ///
- [Fact, TestPriority(0)]
- public async void CanImportFileAsync_File_ShouldReturn_Empty_string()
- {
- // Arrange
-
- var pathImport = "C:\\redcap_download_files";
- string importFileName = "test.txt";
- var record = "1";
- var fieldName = "file_upload";
- var eventName = "event_1_arm_1";
- var repeatingInstrument = "1";
-
- // Act
- var result = await _redcapApi.ImportFileAsync(_token, Content.File, RedcapAction.Import, record, fieldName, eventName, repeatingInstrument, importFileName, pathImport, OnErrorFormat.json);
-
- // Assert
- // Expecting an empty string. This API returns an empty string on success.
- Assert.True(string.IsNullOrEmpty(result));
- }
- ///
- /// Test attempts exports the file previously imported
- ///
- [Fact]
- public async void CanExportFileAsync_File_ShouldReturn_string()
- {
- // Arrange
- var pathExport = "C:\\redcap_download_files";
- var record = "1";
- var fieldName = "file_upload";
- var eventName = "event_1_arm_1";
- var repeatingInstrument = "1";
- var expectedString = "test.txt";
- var pathImport = "C:\\redcap_download_files";
- string importFileName = "test.txt";
- // we need to import a file first so we can test the export api
- await _redcapApi.ImportFileAsync(_token, Content.File, RedcapAction.Import, record, fieldName, eventName, repeatingInstrument, importFileName, pathImport, OnErrorFormat.json);
-
- // Act
- var result = await _redcapApi.ExportFileAsync(_token, Content.File, RedcapAction.Export, record, fieldName, eventName, repeatingInstrument, OnErrorFormat.json, pathExport);
-
- // Assert
- Assert.Contains(expectedString, result);
- }
- ///
- /// Can delete file previously uploaded
- ///
- [Fact]
- public async void CanDeleteFileAsync_File_ShouldReturn_Empty_string()
- {
- // Arrange
-
- var pathImport = "C:\\redcap_download_files";
- string importFileName = "test.txt";
- var record = "1";
- var fieldName = "protocol_upload";
- // In order for us to import a file in a longitudinal format, we'll need to specify
- // which event it belongs.
- var eventName = "event_1_arm_1";
- var repeatingInstrument = "1";
- // Import a file to a record so we can do the integrated test below.
- await _redcapApi.ImportFileAsync(_token, Content.File, RedcapAction.Import, record, fieldName, eventName, repeatingInstrument, importFileName, pathImport, OnErrorFormat.json);
-
- // Act
- var result = await _redcapApi.DeleteFileAsync(_token, Content.File, RedcapAction.Delete, record, fieldName, eventName, repeatingInstrument, OnErrorFormat.json);
-
- // Assert
- Assert.Contains(string.Empty, result);
+ return CreateDemographicsInstrumentFiller().Create();
}
- ///
- /// Can export records for projects that does not contain repeating forms/instruments
- /// API Version 1.0.0+
- ///
- [Fact]
- public async void CanExportRecordsAsync_NonRepeating_Should_Return_String()
+ private static Filler CreateDemographicsInstrumentFiller()
{
- // Arrange
- // Arrange
- var data = new List> { };
- var keyValues = new Dictionary { };
- keyValues.Add("first_name", "Jon");
- keyValues.Add("last_name", "Doe");
- keyValues.Add("record_id", "8");
- data.Add(keyValues);
- // import a record so we can test
- await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
-
- var records = new string[] { "8" };
-
- // Act
- var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
-
- // Assert
- Assert.Contains("1", result);
- }
- ///
- /// Can export records for project with repeating instruments/forms
- /// API Version 1.0.0+
- ///
- [Fact]
- public async void CanExportRecordsAsync_Repeating_Should_Return_Record()
- {
- // Arrange
- var repeatingInstruments = new List {
- new RedcapRepeatInstrument {
- EventName = "event_1_arm_1",
- FormName = "demographics",
- CustomFormLabel = "TestTestTest"
- }
- };
-
- // We import a repeating instrument to 'turn on' repeating instruments feature
- // as well as setting an initial repeating instrument
- await _redcapApi.ImportRepeatingInstrumentsAndEvents(_token, repeatingInstruments);
- // Get the redcap event from above
- var redcapEvent = repeatingInstruments.FirstOrDefault();
- var data = new List> { };
- // passing in minimum requirements for a project with repeating instruments/forms
- var keyValues = new Dictionary { };
- keyValues.Add("first_name", "Jon");
- keyValues.Add("last_name", "Doe");
- keyValues.Add("record_id", "8");
- keyValues.Add("redcap_repeat_instance", "1");
- keyValues.Add("redcap_repeat_instrument", redcapEvent.FormName);
- data.Add(keyValues);
- // import a record so we can test
- await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
- var records = new string[] { "8" };
-
- // Act
- var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
-
- // Assert
- Assert.Contains("1", result);
+ var filler = new Filler();
+ return filler;
}
}
}
diff --git a/Tests/RedcapApiTestsOld.cs b/Tests/RedcapApiTestsOld.cs
new file mode 100644
index 0000000..0272e55
--- /dev/null
+++ b/Tests/RedcapApiTestsOld.cs
@@ -0,0 +1,847 @@
+/*
+ * REDCap API Library
+ * Michael Tran tranpl@vcu.edu, tranpl@outlook.com
+ * Biomedical Informatics Core
+ * C. Kenneth and Dianne Wright Center for Clinical and Translational Research
+ * Virginia Commonwealth University
+ *
+ * Copyright (c) 2021 Virginia Commonwealth University
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+using System.Collections.Generic;
+using System.Linq;
+
+using Newtonsoft.Json;
+
+using Redcap;
+using Redcap.Models;
+using Redcap.Utilities;
+
+using Xunit;
+
+namespace Tests
+{
+ ///
+ /// Very simplified test class for Redcap Api
+ /// This is not a comprehensive test, add more if you'd like.
+ /// Make sure you have some records in the redcap project for the instance you are testing
+ ///
+ public class RedcapApiTestsOld
+ {
+ // API Token for a project in a local instance of redcap
+ private const string _token = "A8E6949EF4380F1111C66D5374E1AE6C";
+ // local instance of redcap api uri
+ private const string _uri = "http://localhost/redcap/api/";
+ private readonly RedcapApi _redcapApi;
+ public RedcapApiTestsOld()
+ {
+ _redcapApi = new RedcapApi(_uri);
+ }
+
+ [Fact]
+ public async void CanImportRecordsAsyncAsDictionary_ShouldReturn_CountString()
+ {
+ // Arrange
+ var data = new List> { };
+ var keyValues = new Dictionary { };
+ keyValues.Add("first_name", "Jon");
+ keyValues.Add("last_name", "Doe");
+ keyValues.Add("record_id", "8");
+ keyValues.Add("redcap_repeat_instrument", "demographics");
+ data.Add(keyValues);
+ // Act
+ var result = await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
+
+ // Assert
+ // Expecting a string of 1 since we are importing one record
+ Assert.Contains("1", result);
+ }
+ [Fact, TestPriority(0)]
+ public void CanImportRecordsAsync_ShouldReturn_CountString()
+ {
+ // Arrange
+ var data = new List { new Demographic { FirstName = "Jon", LastName = "Doe", RecordId = "1" } };
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ // executing method using default options
+ var result = _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json).Result;
+
+ var res = JsonConvert.DeserializeObject(result).ToString();
+
+ // Assert
+ // Expecting a string of 1 since we are importing one record
+ Assert.Contains("1", res);
+ }
+ [Fact]
+ public async void CanDeleteRecordsAsync_ShouldReturn_string()
+ {
+ // Arrange
+ var record = new List> { };
+ var keyValues = new Dictionary { };
+ keyValues.Add("first_name", "Jon");
+ keyValues.Add("last_name", "Doe");
+ keyValues.Add("record_id", "8");
+ record.Add(keyValues);
+ // import records so we can delete it for this test
+ await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, true, record, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
+
+ var records = new string[]
+ {
+ "8"
+ };
+ var arm = 1;
+ // Act
+ var result = await _redcapApi.DeleteRecordsAsync(_token, Content.Record, RedcapAction.Delete, records, arm);
+
+ // Assert
+ // Expecting a string of 1 since we are deleting one record
+ Assert.Contains("1", result);
+ }
+ [Fact]
+ public async void CanImportRepeatingInstrumentsAndEvents_ShouldReturn_string()
+ {
+ // exact instrument names in data dictionary to repeat
+ var repeatingInstruments = new List {
+ new RedcapRepeatInstrument {
+ EventName = "event_1_arm_1",
+ FormName = "demographics",
+ CustomFormLabel = "TestTestTest"
+ }
+ };
+ var result = await _redcapApi.ImportRepeatingInstrumentsAndEvents(_token, repeatingInstruments);
+ // Expect "1" as we are importing a single repeating instrument
+ Assert.Contains("1", result);
+ }
+ ///
+ /// Test ability to export repeating instruments and events
+ /// API Version 1.0.0+
+ ///
+ [Fact]
+ public async void CanExportRepeatingInstrumentsAndEvents_ShouldReturn_string()
+ {
+ // Arrange
+ // We'll importa a single repeating form so we can run test
+ // By executing the import repeating instrument api, redcap will
+ // enable the repeating instruments and events feature automatically
+ var repeatingInstruments = new List {
+ new RedcapRepeatInstrument {
+ EventName = "event_1_arm_1",
+ FormName = "demographics",
+ CustomFormLabel = "TestTestTest"
+ }
+ };
+ await _redcapApi.ImportRepeatingInstrumentsAndEvents(_token, repeatingInstruments);
+
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var result = await _redcapApi.ExportRepeatingInstrumentsAndEvents(_token);
+
+ // Assert
+ // Expecting event names, form name and custom form labels
+ // we imported it above
+ Assert.Contains("event_name", result);
+ Assert.Contains("form_name", result);
+ Assert.Contains("custom_form_label", result);
+ Assert.Contains("demographics", result);
+ Assert.Contains("event_1_arm_1", result);
+ Assert.Contains("TestTestTest", result);
+ }
+
+ ///
+ /// Can Export Arms
+ /// All arms should be returned
+ /// Using API version 1.0.0+
+ ///
+ [Fact, TestPriority(1)]
+ public void CanExportArmsAsync_AllArms_ShouldContain_armnum()
+ {
+ // Arrange
+
+
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var result = _redcapApi.ExportArmsAsync(_token).Result;
+ var data = JsonConvert.DeserializeObject(result).ToString();
+
+ // Assert
+ // Expecting multiple arms to be return since we asked for all arms by not providing any arms by passing null for the params
+ // ** Important to notice is that if we didn't add any events to an arm, even if there are more, only
+ // arms with events will be returned **
+ Assert.Contains("1", data);
+ Assert.Contains("2", data);
+ }
+
+
+ ///
+ /// Can Import Arms
+ /// Using API version 1.0.0+
+ ///
+ [Fact, TestPriority(0)]
+ public async void CanImportArmsAsync_SingleArm_ShouldReturn_number()
+ {
+ // Arrange
+ var armlist = new List
+ {
+ new RedcapArm{ArmNumber = "3", Name = "testarm3_this_will_be_deleted"},
+ new RedcapArm{ArmNumber = "2", Name = "testarm2_this_will_be_deleted"},
+ new RedcapArm{ArmNumber = "4", Name = "testarm4_this_will_be_deleted"},
+ };
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var result = await _redcapApi.ImportArmsAsync(_token, Content.Arm, Override.False, RedcapAction.Import, ReturnFormat.json, armlist, OnErrorFormat.json);
+
+ // Assert
+ // Expecting "3", the number of arms imported, since we pass 3 arm to be imported
+ Assert.Contains("3", result);
+ }
+ ///
+ /// Can Delete Arms
+ /// Using API version 1.0.0+
+ ///
+ [Fact, TestPriority(99)]
+ public async void CanDeleteArmsAsync_SingleArm_ShouldReturn_number()
+ {
+ // Arrange
+ // Initially if we enable a project as longitudinal, redcap will append a single arm.
+ // We are adding a single arm(3) to the project.
+ var redcapArms = new List {
+ new RedcapArm { ArmNumber = "3", Name = "Arm 3" },
+ };
+
+ // Make sure we have an arm with a few events before trying to delete one.
+ var importArmsResults = await _redcapApi.ImportArmsAsync(_token, Content.Arm, Override.True, RedcapAction.Import, ReturnFormat.json, redcapArms, OnErrorFormat.json);
+
+ // arms(#) to be deleted
+ var armarray = new string[]
+ {
+ "3"
+ };
+
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var result = _redcapApi.DeleteArmsAsync(_token, Content.Arm, RedcapAction.Delete, armarray).Result;
+ var data = JsonConvert.DeserializeObject(result).ToString();
+
+ // Assert
+ // Expecting "1", the number of arms deleted, since we pass 1 arm to be deleted
+ // You'll need an arm 3 to be available first, run import arm
+ Assert.Contains("1", data);
+ }
+ ///
+ /// Can Export Events
+ /// Using API version 1.0.0+
+ ///
+ [Fact]
+ public void CanExportEventsAsync_SingleEvent_ShouldReturn_event()
+ {
+ // Arrange
+
+ var ExportEventsAsyncData = new string[] { "1" };
+
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var result = _redcapApi.ExportEventsAsync(_token, Content.Event, ReturnFormat.json, ExportEventsAsyncData, OnErrorFormat.json).Result;
+ var data = JsonConvert.DeserializeObject(result).ToString();
+
+ // Assert
+ Assert.Contains("event_name", data);
+ }
+ ///
+ /// Can Import Events
+ /// Using API version 1.0.0+
+ ///
+ [Fact, TestPriority(0)]
+ public void CanImportEventsAsync_MultipleEvents_ShouldReturn_number()
+ {
+ // Arrange
+
+ var apiEndpoint = _uri;
+ var eventList = new List {
+ new RedcapEvent {
+ EventName = "Event 1",
+ ArmNumber = "1",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "event_1_arm_1",
+ CustomEventLabel = "Baseline"
+ },
+ new RedcapEvent {
+ EventName = "Event 2",
+ ArmNumber = "1",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "event_2_arm_1",
+ CustomEventLabel = "First Visit"
+ },
+ new RedcapEvent {
+ EventName = "Event 3",
+ ArmNumber = "1",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "event_3_arm_1",
+ CustomEventLabel = "Clinical"
+ }
+ };
+
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var _redcapApi = new RedcapApi(apiEndpoint);
+ var result = _redcapApi.ImportEventsAsync(_token, Content.Event, RedcapAction.Import, Override.False, ReturnFormat.json, eventList, OnErrorFormat.json).Result;
+ var data = JsonConvert.DeserializeObject(result).ToString();
+
+ // Assert
+ // Expecting "3", since we had 3 redcap events imported
+ Assert.Contains("3", data);
+ }
+ ///
+ /// Can delete Events
+ /// Using API version 1.0.0+
+ ///
+ [Fact, TestPriority(10)]
+ public async void CanDeleteEventsAsync_SingleEvent_ShouldReturn_number()
+ {
+ // Arrange
+ var events = new List {
+ new RedcapEvent {
+ ArmNumber = "1",
+ EventName = "Clinical Event 1",
+ UniqueEventName = "clinical_arm_1"}
+ };
+ await _redcapApi.ImportEventsAsync(_token, Content.Event, RedcapAction.Import, Override.True, ReturnFormat.json, events);
+ // the event above, redcap appends _arm_1 when you add an arm_#
+ var DeleteEventsAsyncData = new string[] { "clinical_event_1_arm_1" };
+
+ // Act
+ /*
+ * Using API Version 1.0.0+
+ */
+ var result = await _redcapApi.DeleteEventsAsync(_token, Content.Event, RedcapAction.Delete, DeleteEventsAsyncData);
+
+ // Assert
+ // Expecting "1", since we had 1 redcap events imported
+ Assert.Contains("1", result);
+ }
+ [Fact]
+ public void CanExportRecordAsync_SingleRecord_ShouldReturn_String_1()
+ {
+ // Arrange
+ var recordId = "1";
+ // Act
+ /*
+ * Just passing the required parameters
+ */
+ var result = _redcapApi.ExportRecordAsync(_token, Content.Record, recordId).Result;
+
+ // Assert
+ Assert.Contains("1", result);
+
+ }
+ ///
+ /// Export / Get single record
+ ///
+ [Fact]
+ public async void CanExportRecordsAsync_SingleRecord_FromRepeatingInstrument_ShouldContain_string_1()
+ {
+ // Arrange
+ var record = new string[]
+ {
+ "1"
+ };
+ var redcapEvent = new string[] { "event_1_arm_1" };
+ // Act
+ var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, record, null, null, redcapEvent);
+
+ // Assert
+ Assert.Contains("1", result);
+ }
+ ///
+ /// Can export multiple records
+ ///
+ [Fact]
+ public async void CanExportRecordsAsync_MultipleRecord_ShouldContain_string_1_2()
+ {
+ // Arrange
+ var records = new string[] { "1, 2" };
+
+ // Act
+ var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
+
+ // Assert
+ Assert.Contains("1", result);
+ Assert.Contains("2", result);
+ }
+ ///
+ /// Can export single record
+ ///
+ [Fact]
+ public async void CanExportRecordAsync_SingleRecord_ShouldContain_string_1()
+ {
+ // Arrange
+ var records = new string[] { "1" };
+
+ // Act
+ var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
+
+ // Assert
+ Assert.Contains("1", result);
+ }
+
+ ///
+ /// Can export redcap version
+ /// API Version 1.0.0+
+ ///
+ [Fact]
+ public async void CanExportRedcapVersion_VersionNumber_Shouldontain_Number()
+ {
+ // Arrange
+ // Assume current redcap version is 8+
+ var currentRedcapVersion = "8";
+
+ // Act
+ var result = await _redcapApi.ExportRedcapVersionAsync(_token, Content.Version);
+
+ // Assert
+ // Any version 8.XX will do
+ Assert.Contains(currentRedcapVersion, result);
+
+ }
+ ///
+ /// Can export users
+ /// API Version 1.0.0+
+ ///
+ [Fact]
+ public async void CanExportUsers_AllUsers_ShouldReturn_username()
+ {
+ // Arrange
+ var username = "tranpl";
+
+ // Act
+ var result = await _redcapApi.ExportUsersAsync(_token, ReturnFormat.json);
+ // Assert
+ Assert.Contains(username, result);
+
+ }
+
+ ///
+ /// Can export records
+ ///
+ [Fact]
+ public async void CanExportRecordsAsync_AllRecords_ShouldReturn_String()
+ {
+ // Arrange
+
+ // Act
+ var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat);
+ var data = JsonConvert.DeserializeObject>(result);
+
+ // Assert
+ // expecting a list of
+ Assert.True(data.Count > 1);
+ }
+ ///
+ /// Can import meta data
+ /// This method allows you to import metadata (i.e., Data Dictionary) into a project.
+ /// Notice: Because of this method's destructive nature, it is only available for use for projects in Development status.
+ /// API Version 1.0.0+
+ ///
+ [Fact]
+ public async void CanImportMetaDataAsync_Metadata_ShouldReturn_string_record_id()
+ {
+ // Arrange
+ // This will wipe out the current data dictionary and update with the below meta.
+ var metata = new List {
+ new RedcapMetaData{
+ field_name ="record_id",
+ form_name = "demographics",
+ field_label ="Study Id",
+ field_type ="text",
+ section_header = "",
+ },
+ new RedcapMetaData{
+ field_name ="first_name",
+ form_name = "demographics",
+ field_label ="First Name",
+ field_type ="text",
+ section_header = "Contact Information",
+ identifier = "y"
+ },
+ new RedcapMetaData{
+ field_name ="last_name",
+ form_name = "demographics",
+ field_label ="Last Name",
+ field_type ="text",
+ identifier = "y"
+ },
+ new RedcapMetaData{
+ field_name ="address",
+ form_name = "demographics",
+ field_label ="Street, City, State, ZIP",
+ field_type ="notes",
+ identifier = "y"
+ },
+ new RedcapMetaData{
+ field_name ="email",
+ form_name = "demographics",
+ field_label ="E-mail",
+ field_type ="text",
+ identifier = "y",
+ text_validation_type_or_show_slider_number = "email"
+ },
+ new RedcapMetaData{
+ field_name ="dob",
+ form_name = "demographics",
+ field_label ="Date of Birth",
+ field_type ="text",
+ identifier = "y",
+ text_validation_type_or_show_slider_number = "date_ymd"
+ },
+ new RedcapMetaData{
+ field_name ="file_upload",
+ form_name = "demographics",
+ field_label ="File Upload",
+ field_type ="file"
+ }
+ };
+ // Act
+ var result = await _redcapApi.ImportMetaDataAsync(_token, Content.MetaData, ReturnFormat.json, metata);
+
+ // Assert
+ // Expecting 7 metada objects imported
+ Assert.Contains("7", result);
+ }
+ ///
+ /// Can export meta data
+ ///
+ [Fact]
+ public async void CanExportMetaDataAsync_Metadata_ShouldReturn_NumberMetadataFields()
+ {
+ // This will wipe out the current data dictionary and update with the below meta.
+ var metata = new List {
+ new RedcapMetaData{
+ field_name ="record_id",
+ form_name = "demographics",
+ field_label ="Study Id",
+ field_type ="text",
+ section_header = "",
+ },
+ new RedcapMetaData{
+ field_name ="first_name",
+ form_name = "demographics",
+ field_label ="First Name",
+ field_type ="text",
+ section_header = "Contact Information",
+ identifier = "y"
+ },
+ new RedcapMetaData{
+ field_name ="last_name",
+ form_name = "demographics",
+ field_label ="Last Name",
+ field_type ="text",
+ identifier = "y"
+ },
+ new RedcapMetaData{
+ field_name ="address",
+ form_name = "demographics",
+ field_label ="Street, City, State, ZIP",
+ field_type ="notes",
+ identifier = "y"
+ },
+ new RedcapMetaData{
+ field_name ="email",
+ form_name = "demographics",
+ field_label ="E-mail",
+ field_type ="text",
+ identifier = "y",
+ text_validation_type_or_show_slider_number = "email"
+ },
+ new RedcapMetaData{
+ field_name ="dob",
+ form_name = "demographics",
+ field_label ="Date of Birth",
+ field_type ="text",
+ identifier = "y",
+ text_validation_type_or_show_slider_number = "date_ymd"
+ },
+ new RedcapMetaData{
+ field_name ="file_upload",
+ form_name = "demographics",
+ field_label ="File Upload",
+ field_type ="file"
+ }
+ };
+ // import 7 metadata fields into the project
+ // this creates an instrument named demographics with 7 fields
+ await _redcapApi.ImportMetaDataAsync(_token, Content.MetaData, ReturnFormat.json, metata);
+
+ // Act
+ var result = await _redcapApi.ExportMetaDataAsync(_token, ReturnFormat.json);
+ var data = JsonConvert.DeserializeObject>(result);
+ // Assert
+ // expecting 7 metadata to be exported
+ Assert.True(data.Count >= 7);
+ }
+ ///
+ /// Can export arms
+ ///
+ [Fact]
+ public async void CanExportArmsAsync_Arms_ShouldReturn_arms_array()
+ {
+ // Arrange
+ // Importing 3 arms so that we can run the test to export
+ var redcapArms = new List
+ {
+ new RedcapArm{ArmNumber = "3", Name = "testarm3_this_will_be_deleted"},
+ new RedcapArm{ArmNumber = "2", Name = "testarm2_this_will_be_deleted"},
+ new RedcapArm{ArmNumber = "4", Name = "testarm4_this_will_be_deleted"},
+ };
+ // Import Arms so we can test
+ await _redcapApi.ImportArmsAsync(_token, Content.Arm, Override.False, RedcapAction.Import, ReturnFormat.json, redcapArms, OnErrorFormat.json);
+ var listOfEvents = new List() {
+ new RedcapEvent{
+ ArmNumber = "2",
+ CustomEventLabel = "HelloEvent1",
+ EventName = "Import Event 1",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "import_event_1_arm_2"
+ },
+ new RedcapEvent{
+ ArmNumber = "2",
+ CustomEventLabel = "HelloEvent2",
+ EventName = "Import Event 2",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "import_event_2_arm_2"
+ },
+ new RedcapEvent{
+ ArmNumber = "2",
+ CustomEventLabel = "HelloEvent3",
+ EventName = "Import Event 3",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "import_event_3_arm_2"
+ }
+ };
+ // Import Events so we can test
+ await _redcapApi.ImportEventsAsync(_token, Override.False, ReturnFormat.json, listOfEvents, OnErrorFormat.json);
+ // we want to export arm 1 and arm 2
+ var exportArms = new string[] { "1", "2" };
+ // Act
+ var result = await _redcapApi.ExportArmsAsync(_token, Content.Arm, ReturnFormat.json, exportArms);
+
+ // Assert
+ // In order for the arms array to be returned, events for the specific arm
+ // needs to be present. An arm without any events will not be returned.
+ Assert.Contains("arm_num", result);
+ Assert.Contains("1", result);
+ Assert.Contains("2", result);
+ }
+ ///
+ /// Can import arms
+ ///
+ [Fact, TestPriority(0)]
+ public async void CanImportEventsAsync_Events_ShouldReturn_Number()
+ {
+ // Arrange
+ var listOfEvents = new List() {
+ new RedcapEvent{
+ ArmNumber = "3",
+ CustomEventLabel = "HelloEvent1",
+ EventName = "Import Event 1",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "import_event_1_arm_3"
+ },
+ new RedcapEvent{
+ ArmNumber = "3",
+ CustomEventLabel = "HelloEvent2",
+ EventName = "Import Event 2",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "import_event_2_arm_3"
+ },
+ new RedcapEvent{
+ ArmNumber = "3",
+ CustomEventLabel = "HelloEvent3",
+ EventName = "Import Event 3",
+ DayOffset = "1",
+ MinimumOffset = "0",
+ MaximumOffset = "0",
+ UniqueEventName = "import_event_3_arm_3"
+ }
+ };
+
+ // Act
+ var result = await _redcapApi.ImportEventsAsync(_token, Override.False, ReturnFormat.json, listOfEvents, OnErrorFormat.json);
+
+ // Assert
+ // Expecting 3 since we imported 3 events
+ Assert.Contains("3", result);
+ }
+ ///
+ /// Test attempts to import a file into the redcap project
+ /// There are a few assumptions, please make sure you have the files and folders
+ /// exactly as shown, or name it to your needs.
+ /// API Version 1.0.0+
+ ///
+ [Fact, TestPriority(0)]
+ public async void CanImportFileAsync_File_ShouldReturn_Empty_string()
+ {
+ // Arrange
+
+ var pathImport = "C:\\redcap_download_files";
+ string importFileName = "test.txt";
+ var record = "1";
+ var fieldName = "file_upload";
+ var eventName = "event_1_arm_1";
+ var repeatingInstrument = "1";
+
+ // Act
+ var result = await _redcapApi.ImportFileAsync(_token, Content.File, RedcapAction.Import, record, fieldName, eventName, repeatingInstrument, importFileName, pathImport, OnErrorFormat.json);
+
+ // Assert
+ // Expecting an empty string. This API returns an empty string on success.
+ Assert.True(string.IsNullOrEmpty(result));
+ }
+ ///
+ /// Test attempts exports the file previously imported
+ ///
+ [Fact]
+ public async void CanExportFileAsync_File_ShouldReturn_string()
+ {
+ // Arrange
+ var pathExport = "C:\\redcap_download_files";
+ var record = "1";
+ var fieldName = "file_upload";
+ var eventName = "event_1_arm_1";
+ var repeatingInstrument = "1";
+ var expectedString = "test.txt";
+ var pathImport = "C:\\redcap_download_files";
+ string importFileName = "test.txt";
+ // we need to import a file first so we can test the export api
+ await _redcapApi.ImportFileAsync(_token, Content.File, RedcapAction.Import, record, fieldName, eventName, repeatingInstrument, importFileName, pathImport, OnErrorFormat.json);
+
+ // Act
+ var result = await _redcapApi.ExportFileAsync(_token, Content.File, RedcapAction.Export, record, fieldName, eventName, repeatingInstrument, OnErrorFormat.json, pathExport);
+
+ // Assert
+ Assert.Contains(expectedString, result);
+ }
+ ///
+ /// Can delete file previously uploaded
+ ///
+ [Fact]
+ public async void CanDeleteFileAsync_File_ShouldReturn_Empty_string()
+ {
+ // Arrange
+
+ var pathImport = "C:\\redcap_download_files";
+ string importFileName = "test.txt";
+ var record = "1";
+ var fieldName = "protocol_upload";
+ // In order for us to import a file in a longitudinal format, we'll need to specify
+ // which event it belongs.
+ var eventName = "event_1_arm_1";
+ var repeatingInstrument = "1";
+ // Import a file to a record so we can do the integrated test below.
+ await _redcapApi.ImportFileAsync(_token, Content.File, RedcapAction.Import, record, fieldName, eventName, repeatingInstrument, importFileName, pathImport, OnErrorFormat.json);
+
+ // Act
+ var result = await _redcapApi.DeleteFileAsync(_token, Content.File, RedcapAction.Delete, record, fieldName, eventName, repeatingInstrument, OnErrorFormat.json);
+
+ // Assert
+ Assert.Contains(string.Empty, result);
+ }
+ ///
+ /// Can export records for projects that does not contain repeating forms/instruments
+ /// API Version 1.0.0+
+ ///
+ [Fact]
+ public async void CanExportRecordsAsync_NonRepeating_Should_Return_String()
+ {
+ // Arrange
+ // Arrange
+ var data = new List> { };
+ var keyValues = new Dictionary { };
+ keyValues.Add("first_name", "Jon");
+ keyValues.Add("last_name", "Doe");
+ keyValues.Add("record_id", "8");
+ data.Add(keyValues);
+ // import a record so we can test
+ await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
+
+ var records = new string[] { "8" };
+
+ // Act
+ var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
+
+ // Assert
+ Assert.Contains("1", result);
+ }
+ ///
+ /// Can export records for project with repeating instruments/forms
+ /// API Version 1.0.0+
+ ///
+ [Fact]
+ public async void CanExportRecordsAsync_Repeating_Should_Return_Record()
+ {
+ // Arrange
+ var repeatingInstruments = new List {
+ new RedcapRepeatInstrument {
+ EventName = "event_1_arm_1",
+ FormName = "demographics",
+ CustomFormLabel = "TestTestTest"
+ }
+ };
+
+ // We import a repeating instrument to 'turn on' repeating instruments feature
+ // as well as setting an initial repeating instrument
+ await _redcapApi.ImportRepeatingInstrumentsAndEvents(_token, repeatingInstruments);
+ // Get the redcap event from above
+ var redcapEvent = repeatingInstruments.FirstOrDefault();
+ var data = new List> { };
+ // passing in minimum requirements for a project with repeating instruments/forms
+ var keyValues = new Dictionary { };
+ keyValues.Add("first_name", "Jon");
+ keyValues.Add("last_name", "Doe");
+ keyValues.Add("record_id", "8");
+ keyValues.Add("redcap_repeat_instance", "1");
+ keyValues.Add("redcap_repeat_instrument", redcapEvent.FormName);
+ data.Add(keyValues);
+ // import a record so we can test
+ await _redcapApi.ImportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, OverwriteBehavior.normal, false, data, "MDY", CsvDelimiter.comma, ReturnContent.count, OnErrorFormat.json);
+ var records = new string[] { "8" };
+
+ // Act
+ var result = await _redcapApi.ExportRecordsAsync(_token, Content.Record, ReturnFormat.json, RedcapDataType.flat, records);
+
+ // Assert
+ Assert.Contains("1", result);
+ }
+ }
+}
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index fc48088..771141a 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.0
+ net5.0
false
@@ -11,14 +11,20 @@
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
diff --git a/ram_crest_160.png b/ram_crest_160.png
deleted file mode 100644
index 90c3c9b..0000000
Binary files a/ram_crest_160.png and /dev/null differ
diff --git a/vcu.png b/vcu.png
new file mode 100644
index 0000000..11ec984
Binary files /dev/null and b/vcu.png differ