From ca7b6be43b7cf9d03257360e17fbef1fba8041ff Mon Sep 17 00:00:00 2001
From: Teo Voinea <58236992+tevoinea@users.noreply.github.com>
Date: Wed, 14 Sep 2022 11:07:52 -0400
Subject: [PATCH] Refactor notification support (#2363)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add teams notifications
* .
* Fix compilation isues
* Checkpoint
* Added Ado
* Fix some TODOs
* Teams messages work! 🎉
* fmt
* Bug fix container url generator
* Some small ado changes
* 🧹
* PR comments
* Fix packages
* Get more detailed restore information to debug errors
* Maybe fixes this issue?
* Undo CI change
---
docs/notifications/ado.md | 14 +-
src/ApiService/ApiService/ApiService.csproj | 2 +
.../ApiService/Functions/QueueFileChanges.cs | 8 +-
.../ApiService/OneFuzzTypes/Model.cs | 87 ++++--
src/ApiService/ApiService/Program.cs | 3 +
.../ApiService/onefuzzlib/Containers.cs | 12 +
.../ApiService/onefuzzlib/Extension.cs | 2 +-
.../onefuzzlib/NotificationOperations.cs | 21 +-
.../ApiService/onefuzzlib/OnefuzzContext.cs | 6 +
.../ApiService/onefuzzlib/Reports.cs | 12 -
.../ApiService/onefuzzlib/Scheduler.cs | 2 +-
.../onefuzzlib/notifications/Ado.cs | 283 ++++++++++++++++++
.../onefuzzlib/notifications/GithubIssues.cs | 160 ++++++++++
.../notifications/NotificationsBase.cs | 99 ++++++
.../onefuzzlib/notifications/Teams.cs | 124 ++++++++
src/ApiService/ApiService/packages.lock.json | 251 +++++++++-------
.../IntegrationTests/Fakes/TestContext.cs | 3 +
.../IntegrationTests/packages.lock.json | 258 +++++++++++-----
src/ApiService/Tests/OrmModelsTest.cs | 92 +++++-
src/ApiService/Tests/TemplateTests.cs | 15 +
src/ApiService/Tests/packages.lock.json | 258 +++++++++++-----
21 files changed, 1382 insertions(+), 330 deletions(-)
create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs
create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs
create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs
diff --git a/docs/notifications/ado.md b/docs/notifications/ado.md
index 892948826..3899dc8f0 100644
--- a/docs/notifications/ado.md
+++ b/docs/notifications/ado.md
@@ -1,16 +1,16 @@
# Azure Devops Work Item creation
Automatic creation of ADO Work Items from OneFuzz allows for the user to
-customize any field using [jinja2](https://jinja.palletsprojects.com/)
+customize any field using [scriban](https://github.com/scriban/scriban)
templates.
-There are multiple Python objects provided via the template engine that
+There are multiple objects provided via the template engine that
can be used such that any arbitrary component can be used to flesh out
the configuration:
-* task (See [TaskConfig](../../src/pytypes/onefuzztypes/models.py))
-* report (See [Report](../../src/pytypes/onefuzztypes/models.py))
-* job (See [JobConfig](../../src/pytypes/onefuzztypes/models.py))
+* task (See [TaskConfig](../../src/ApiService/ApiService/OneFuzzTypes/Model.cs))
+* report (See [Report](../../src/ApiService/ApiService/OneFuzzTypes/Model.cs))
+* job (See [JobConfig](../../src/ApiService/ApiService/OneFuzzTypes/Model.cs))
Using these objects allows dynamic configuration. As an example, the `project`
could be specified directly, or dynamically pulled from a template:
@@ -49,7 +49,7 @@ clickable, make it a link.
"Microsoft.VSTS.Scheduling.StoryPoints": "1",
"System.IterationPath": "Iteration\\Path\\Here",
"System.Title": "{{ report.crash_site }} - {{ report.executable }}",
- "Microsoft.VSTS.TCM.ReproSteps": "This is my call stack:
{% for item in report.call_stack %} - {{ item }}
{% endfor %}
"
+ "Microsoft.VSTS.TCM.ReproSteps": "This is my call stack: {{ for item in report.call_stack }} - {{ item }}
{{ endfor }}
"
},
"comment": "This is my comment. {{ report.input_sha256 }} {{ input_url }}
{{ repro_cmd }}
",
"unique_fields": ["System.Title", "System.AreaPath"],
@@ -140,7 +140,7 @@ onefuzz notifications create_ado oft-my-demo-job-reports \
Microsoft.VSTS.Scheduling.StoryPoints=1 \
"System.IterationPath=Iteration\\Path\\Here" \
"System.Title={{ report.crash_site }} - {{ report.executable }}" \
- "Microsoft.VSTS.TCM.ReproSteps=This is my call stack: {% for item in report.call_stack %} - {{ item }}
{% endfor %}
" \
+ "Microsoft.VSTS.TCM.ReproSteps=This is my call stack: {{ for item in report.call_stack }} - {{ item }}
{{ end }}
" \
--comment "This is my comment. {{ report.input_sha256 }} {{ input_url }}" \
--on_dup_comment "Another POC was found in target" \
--on_dup_set_state Resolved=Active \
diff --git a/src/ApiService/ApiService/ApiService.csproj b/src/ApiService/ApiService/ApiService.csproj
index f38e51e64..1135ba821 100644
--- a/src/ApiService/ApiService/ApiService.csproj
+++ b/src/ApiService/ApiService/ApiService.csproj
@@ -42,6 +42,8 @@
+
+
diff --git a/src/ApiService/ApiService/Functions/QueueFileChanges.cs b/src/ApiService/ApiService/Functions/QueueFileChanges.cs
index 22832d3a9..897ed2ce6 100644
--- a/src/ApiService/ApiService/Functions/QueueFileChanges.cs
+++ b/src/ApiService/ApiService/Functions/QueueFileChanges.cs
@@ -22,9 +22,9 @@ public class QueueFileChanges {
_notificationOperations = notificationOperations;
}
- //[Function("QueueFileChanges")]
+ [Function("QueueFileChanges")]
public async Async.Task Run(
- [QueueTrigger("file-changes-refactored", Connection = "AzureWebJobsStorage")] string msg,
+ [QueueTrigger("file-changes", Connection = "AzureWebJobsStorage")] string msg,
int dequeueCount) {
var fileChangeEvent = JsonSerializer.Deserialize(msg, EntityConverter.GetJsonSerializerOptions());
var lastTry = dequeueCount == MAX_DEQUEUE_COUNT;
@@ -44,10 +44,10 @@ public class QueueFileChanges {
return;
}
- await file_added(_log, fileChangeEvent, lastTry);
+ await FileAdded(_log, fileChangeEvent, lastTry);
}
- private async Async.Task file_added(ILogTracer log, JsonDocument fileChangeEvent, bool failTaskOnTransientError) {
+ private async Async.Task FileAdded(ILogTracer log, JsonDocument fileChangeEvent, bool failTaskOnTransientError) {
var data = fileChangeEvent.RootElement.GetProperty("data");
var url = data.GetProperty("url").GetString()!;
var parts = url.Split("/").Skip(3).ToList();
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
index 77c4f4435..f39d4f88f 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
@@ -84,7 +84,11 @@ public record ProxyHeartbeat
Guid ProxyId,
List Forwards,
DateTimeOffset TimeStamp
-);
+) {
+ public override string ToString() {
+ return JsonSerializer.Serialize(this);
+ }
+};
public record Node
(
@@ -412,7 +416,7 @@ public record Notification(
public record BlobRef(
string Account,
- Container container,
+ Container Container,
string Name
);
@@ -470,21 +474,43 @@ public class NotificationTemplateConverter : JsonConverter
public override NotificationTemplate? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
using var templateJson = JsonDocument.ParseValue(ref reader);
try {
- return templateJson.Deserialize(options);
- } catch (JsonException) {
+ return ValidateDeserialization(templateJson.Deserialize(options));
+ } catch (Exception ex) when (
+ ex is JsonException
+ || ex is ArgumentNullException
+ || ex is ArgumentOutOfRangeException
+ ) {
}
try {
- return templateJson.Deserialize(options);
- } catch (JsonException) {
+ return ValidateDeserialization(templateJson.Deserialize(options));
+ } catch (Exception ex) when (
+ ex is JsonException
+ || ex is ArgumentNullException
+ || ex is ArgumentOutOfRangeException
+ ) {
+
}
try {
- return templateJson.Deserialize(options);
- } catch (JsonException) {
+ return ValidateDeserialization(templateJson.Deserialize(options));
+ } catch (Exception ex) when (
+ ex is JsonException
+ || ex is ArgumentNullException
+ || ex is ArgumentOutOfRangeException
+ ) {
+
}
- throw new JsonException("Unsupported notification template");
+
+ var expectedTemplateTypes = new List {
+ typeof(AdoTemplate),
+ typeof(TeamsTemplate),
+ typeof(GithubIssuesTemplate)
+ }
+ .Select(type => type.ToString());
+
+ throw new JsonException($"Unsupported notification template. Could not deserialize {templateJson} into one of the following template types: {string.Join(", ", expectedTemplateTypes)}");
}
public override void Write(Utf8JsonWriter writer, NotificationTemplate value, JsonSerializerOptions options) {
@@ -499,14 +525,38 @@ public class NotificationTemplateConverter : JsonConverter
}
}
+
+ private static T ValidateDeserialization(T? obj) {
+ if (obj == null) {
+ throw new ArgumentNullException($"Failed to deserialize type: {typeof(T)}. It was null.");
+ }
+ var nonNullableParameters = obj.GetType().GetConstructors().First().GetParameters()
+ .Where(parameter => !parameter.HasDefaultValue)
+ .Select(parameter => parameter.Name)
+ .Where(pName => pName != null)
+ .ToHashSet();
+
+ var nullProperties = obj.GetType().GetProperties()
+ .Where(property => property.GetValue(obj) == null)
+ .Select(property => property.Name)
+ .ToHashSet();
+
+ var nullNonNullableProperties = nonNullableParameters.Intersect(nullProperties);
+
+ if (nullNonNullableProperties.Any()) {
+ throw new ArgumentOutOfRangeException($"Failed to deserialize type: {obj.GetType()}. The following non nullable properties are missing values: {string.Join(", ", nullNonNullableProperties)}");
+ }
+
+ return obj;
+ }
}
public record ADODuplicateTemplate(
List Increment,
- string? Comment,
Dictionary SetState,
- Dictionary AdoFields
+ Dictionary AdoFields,
+ string? Comment = null
);
public record AdoTemplate(
@@ -515,27 +565,26 @@ public record AdoTemplate(
string Project,
string Type,
List UniqueFields,
- string? Comment,
Dictionary AdoFields,
- ADODuplicateTemplate OnDuplicate
+ ADODuplicateTemplate OnDuplicate,
+ string? Comment = null
) : NotificationTemplate;
-
public record TeamsTemplate(SecretData Url) : NotificationTemplate;
public record GithubAuth(string User, string PersonalAccessToken);
public record GithubIssueSearch(
- string? Author,
- GithubIssueState? State,
List FieldMatch,
- [property: JsonPropertyName("string")] String str
+ [property: JsonPropertyName("string")] String str,
+ string? Author = null,
+ GithubIssueState? State = null
);
public record GithubIssueDuplicate(
- string? Comment,
List Labels,
- bool Reopen
+ bool Reopen,
+ string? Comment = null
);
diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs
index 103a6c84a..94235d6f2 100644
--- a/src/ApiService/ApiService/Program.cs
+++ b/src/ApiService/ApiService/Program.cs
@@ -102,6 +102,9 @@ public class Program {
.AddScoped()
.AddScoped()
.AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
diff --git a/src/ApiService/ApiService/onefuzzlib/Containers.cs b/src/ApiService/ApiService/onefuzzlib/Containers.cs
index d2a99689a..8d3ce5a26 100644
--- a/src/ApiService/ApiService/onefuzzlib/Containers.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Containers.cs
@@ -29,6 +29,8 @@ public interface IContainers {
public Async.Task AddContainerSasUrl(Uri uri, TimeSpan? duration = null);
public Async.Task>> GetContainers(StorageType corpus);
+
+ public string AuthDownloadUrl(Container container, string filename);
}
public class Containers : IContainers {
@@ -215,4 +217,14 @@ public class Containers : IContainers {
return new(data.SelectMany(x => x));
}
+
+ public string AuthDownloadUrl(Container container, string filename) {
+ var instance = _config.OneFuzzInstance;
+
+ var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);
+ queryString.Add("container", container.String);
+ queryString.Add("filename", filename);
+
+ return $"{instance}/api/download?{queryString}";
+ }
}
diff --git a/src/ApiService/ApiService/onefuzzlib/Extension.cs b/src/ApiService/ApiService/onefuzzlib/Extension.cs
index 7351094ce..622f1137a 100644
--- a/src/ApiService/ApiService/onefuzzlib/Extension.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Extension.cs
@@ -395,7 +395,7 @@ public class Extensions : IExtensions {
BlobSasPermissions.Read
),
await _context.Containers.GetFileSasUrl(
- report?.InputBlob?.container!,
+ report?.InputBlob?.Container!,
report?.InputBlob?.Name!,
StorageType.Corpus,
BlobSasPermissions.Read
diff --git a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
index 26d65075b..9c5f80cdd 100644
--- a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
+++ b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
@@ -1,6 +1,5 @@
using System.Text.Json;
using ApiService.OneFuzzLib.Orm;
-using Azure.Data.Tables;
using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
@@ -36,7 +35,7 @@ public class NotificationOperations : Orm, INotificationOperations
done.Add(notification.Config);
if (notification.Config is TeamsTemplate teamsTemplate) {
- NotifyTeams(teamsTemplate, container, filename, reportOrRegression!);
+ await _context.Teams.NotifyTeams(teamsTemplate, container, filename, reportOrRegression!);
}
if (reportOrRegression == null) {
@@ -44,11 +43,11 @@ public class NotificationOperations : Orm, INotificationOperations
}
if (notification.Config is AdoTemplate adoTemplate) {
- NotifyAdo(adoTemplate, container, filename, reportOrRegression, failTaskOnTransientError);
+ await _context.Ado.NotifyAdo(adoTemplate, container, filename, reportOrRegression, failTaskOnTransientError);
}
if (notification.Config is GithubIssuesTemplate githubIssuesTemplate) {
- GithubIssue(githubIssuesTemplate, container, filename, reportOrRegression);
+ await _context.GithubIssues.GithubIssue(githubIssuesTemplate, container, filename, reportOrRegression);
}
}
@@ -78,7 +77,7 @@ public class NotificationOperations : Orm, INotificationOperations
}
public IAsyncEnumerable GetNotifications(Container container) {
- return QueryAsync(filter: TableClient.CreateQueryFilter($"container eq {container.String}"));
+ return SearchByRowKeys(new[] { container.String });
}
public IAsyncEnumerable<(Task, IEnumerable)> GetQueueTasks() {
@@ -140,16 +139,4 @@ public class NotificationOperations : Orm, INotificationOperations
_logTracer.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
return null;
}
-
- private void GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport report) {
- throw new NotImplementedException();
- }
-
- private void NotifyAdo(AdoTemplate config, Container container, string filename, IReport report, bool failTaskOnTransientError) {
- throw new NotImplementedException();
- }
-
- private void NotifyTeams(TeamsTemplate config, Container container, string filename, IReport report) {
- throw new NotImplementedException();
- }
}
diff --git a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs
index 013e4b3bd..01c9e6dc7 100644
--- a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs
+++ b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs
@@ -43,6 +43,9 @@ public interface IOnefuzzContext {
ISubnet Subnet { get; }
IImageOperations ImageOperations { get; }
EntityConverter EntityConverter { get; }
+ ITeams Teams { get; }
+ IGithubIssues GithubIssues { get; }
+ IAdo Ado { get; }
}
public class OnefuzzContext : IOnefuzzContext {
@@ -89,4 +92,7 @@ public class OnefuzzContext : IOnefuzzContext {
public ISubnet Subnet => _serviceProvider.GetRequiredService();
public IImageOperations ImageOperations => _serviceProvider.GetRequiredService();
public EntityConverter EntityConverter => _serviceProvider.GetRequiredService();
+ public ITeams Teams => _serviceProvider.GetRequiredService();
+ public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService();
+ public IAdo Ado => _serviceProvider.GetRequiredService();
}
diff --git a/src/ApiService/ApiService/onefuzzlib/Reports.cs b/src/ApiService/ApiService/onefuzzlib/Reports.cs
index 333b63726..636a4b407 100644
--- a/src/ApiService/ApiService/onefuzzlib/Reports.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Reports.cs
@@ -59,18 +59,6 @@ public class Reports : IReports {
}
return regressionReport;
}
-
- private IReport? ParseReportOrRegression(IEnumerable content, string? filePath, bool expectReports = false) {
- try {
- var str = System.Text.Encoding.UTF8.GetString(content.ToArray());
- return ParseReportOrRegression(str, filePath, expectReports);
- } catch (Exception e) {
- if (expectReports) {
- _log.Error($"unable to parse report ({filePath}): unicode decode of report failed - {e.Message} {e.StackTrace}");
- }
- return null;
- }
- }
}
public interface IReport { };
diff --git a/src/ApiService/ApiService/onefuzzlib/Scheduler.cs b/src/ApiService/ApiService/onefuzzlib/Scheduler.cs
index 61261c171..2e23636e5 100644
--- a/src/ApiService/ApiService/onefuzzlib/Scheduler.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Scheduler.cs
@@ -248,7 +248,7 @@ public class Scheduler : IScheduler {
});
}
- static Container GetSetupContainer(TaskConfig config) {
+ public static Container GetSetupContainer(TaskConfig config) {
foreach (var container in config.Containers ?? throw new Exception("Missing containers")) {
if (container.Type == ContainerType.Setup) {
diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
new file mode 100644
index 000000000..faa96c4e8
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
@@ -0,0 +1,283 @@
+using System.Text.Json;
+using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
+using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
+using Microsoft.VisualStudio.Services.Common;
+using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
+
+namespace Microsoft.OneFuzz.Service;
+
+public interface IAdo {
+ public Async.Task NotifyAdo(AdoTemplate config, Container container, string filename, IReport reportable, bool failTaskOnTransientError);
+
+}
+
+public class Ado : NotificationsBase, IAdo {
+ public Ado(ILogTracer logTracer, IOnefuzzContext context) : base(logTracer, context) {
+ }
+
+ public async Async.Task NotifyAdo(AdoTemplate config, Container container, string filename, IReport reportable, bool failTaskOnTransientError) {
+ if (reportable is RegressionReport) {
+ _logTracer.Info($"ado integration does not support regression report. container:{container} filename:{filename}");
+ return;
+ }
+
+ var report = (Report)reportable;
+
+ var notificationInfo = @$"job_id:{report.JobId} task_id:{report.TaskId}
+container:{container} filename:{filename}";
+
+ _logTracer.Info($"notify ado: {notificationInfo}");
+
+ try {
+ var ado = await AdoConnector.AdoConnectorCreator(_context, container, filename, config, report, _logTracer);
+ await ado.Process(notificationInfo);
+ } catch (Exception e) {
+ /*
+ TODO: Catch these
+ AzureDevOpsAuthenticationError,
+ AzureDevOpsClientError,
+ AzureDevOpsServiceError,
+ AzureDevOpsClientRequestError,
+ ValueError,
+ */
+ if (!failTaskOnTransientError && IsTransient(e)) {
+ _logTracer.Error($"transient ADO notification failure {notificationInfo}");
+ throw;
+ } else {
+ await FailTask(report, e);
+ }
+ }
+ }
+
+ private static bool IsTransient(Exception e) {
+ var errorCodes = new List()
+ {
+ //TF401349: An unexpected error has occurred, please verify your request and try again.
+ "TF401349",
+ //TF26071: This work item has been changed by someone else since you opened it. You will need to refresh it and discard your changes.
+ "TF26071",
+ };
+
+ var errorStr = e.ToString();
+ return errorCodes.Any(code => errorStr.Contains(code));
+ }
+
+ class AdoConnector {
+ private readonly AdoTemplate _config;
+ private readonly Renderer _renderer;
+ private readonly string _project;
+ private readonly WorkItemTrackingHttpClient _client;
+ private readonly Uri _instanceUrl;
+ private readonly ILogTracer _logTracer;
+ public static async Async.Task AdoConnectorCreator(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogTracer logTracer, Renderer? renderer = null) {
+ renderer ??= await Renderer.ConstructRenderer(context, container, filename, report);
+ var instanceUrl = context.Creds.GetInstanceUrl();
+ var project = await renderer.Render(config.Project, instanceUrl);
+
+ var authToken = await context.SecretsOperations.GetSecretStringValue(config.AuthToken);
+ var client = GetAdoClient(config.BaseUrl, authToken!);
+ return new AdoConnector(container, filename, config, report, renderer, project!, client, instanceUrl, logTracer);
+ }
+
+ private static WorkItemTrackingHttpClient GetAdoClient(Uri baseUrl, string token) {
+ return new WorkItemTrackingHttpClient(baseUrl, new VssBasicCredential("PAT", token));
+ }
+ public AdoConnector(Container container, string filename, AdoTemplate config, Report report, Renderer renderer, string project, WorkItemTrackingHttpClient client, Uri instanceUrl, ILogTracer logTracer) {
+ _config = config;
+ _renderer = renderer;
+ _project = project;
+ _client = client;
+ _instanceUrl = instanceUrl;
+ _logTracer = logTracer;
+ }
+
+ public async Async.Task Render(string template) {
+ return await _renderer.Render(template, _instanceUrl);
+ }
+
+ public async IAsyncEnumerable ExistingWorkItems() {
+ var filters = new Dictionary();
+ foreach (var key in _config.UniqueFields) {
+ var filter = string.Empty;
+ if (string.Equals("System.TeamProject", key)) {
+ filter = await Render(_config.Project);
+ } else {
+ filter = await Render(_config.AdoFields[key]);
+ }
+ filters.Add(key.ToLowerInvariant(), filter);
+ }
+
+ var validFields = await GetValidFields(filters["system.teamproject"]);
+
+ var postQueryFilter = new Dictionary();
+ /*
+ # WIQL (Work Item Query Language) is an SQL like query language that
+ # doesn't support query params, safe quoting, or any other SQL-injection
+ # protection mechanisms.
+ #
+ # As such, build the WIQL with a those fields we can pre-determine are
+ # "safe" and otherwise use post-query filtering.
+ */
+
+ var parts = new List();
+ foreach (var key in filters.Keys) {
+ //# Only add pre-system approved fields to the query
+ if (!validFields.Contains(key)) {
+ postQueryFilter.Add(key, filters[key]);
+ continue;
+ }
+
+ /*
+ # WIQL supports wrapping values in ' or " and escaping ' by doubling it
+ #
+ # For this System.Title: hi'there
+ # use this query fragment: [System.Title] = 'hi''there'
+ #
+ # For this System.Title: hi"there
+ # use this query fragment: [System.Title] = 'hi"there'
+ #
+ # For this System.Title: hi'"there
+ # use this query fragment: [System.Title] = 'hi''"there'
+ */
+ var single = "'";
+ parts.Add($"[{key}] = '{filters[key].Replace(single, single + single)}'");
+ }
+
+ var query = "select [System.Id] from WorkItems";
+ if (parts != null && parts.Any()) {
+ query += " where " + string.Join(" AND ", parts);
+ }
+
+ var wiql = new Wiql() {
+ Query = query
+ };
+
+ foreach (var workItemReference in (await _client.QueryByWiqlAsync(wiql)).WorkItems) {
+ var item = await _client.GetWorkItemAsync(_project, workItemReference.Id, expand: WorkItemExpand.Fields);
+
+ var loweredFields = item.Fields.ToDictionary(kvp => kvp.Key.ToLowerInvariant(), kvp => JsonSerializer.Serialize(kvp.Value));
+ if (postQueryFilter.Any() && !postQueryFilter.All(kvp => {
+ var lowerKey = kvp.Key.ToLowerInvariant();
+ return loweredFields.ContainsKey(lowerKey) && loweredFields[lowerKey] == postQueryFilter[kvp.Key];
+ })) {
+ continue;
+ }
+
+ yield return item;
+ }
+ }
+
+ public async Async.Task UpdateExisting(WorkItem item, string notificationInfo) {
+ if (_config.OnDuplicate.Comment != null) {
+ var comment = await Render(_config.OnDuplicate.Comment);
+ await _client.AddCommentAsync(
+ new CommentCreate() {
+ Text = comment
+ },
+ _project,
+ (int)(item.Id!)
+ );
+ }
+
+ var document = new JsonPatchDocument();
+ foreach (var field in _config.OnDuplicate.Increment) {
+ var value = item.Fields.ContainsKey(field) ? int.Parse(JsonSerializer.Serialize(item.Fields[field])) : 0;
+ value++;
+ document.Add(new JsonPatchOperation() {
+ Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace,
+ Path = $"/fields/{field}",
+ Value = value.ToString()
+ });
+ }
+
+ foreach (var field in _config.OnDuplicate.AdoFields) {
+ var fieldValue = await Render(_config.OnDuplicate.AdoFields[field.Key]);
+ document.Add(new JsonPatchOperation() {
+ Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace,
+ Path = $"/fields/{field}",
+ Value = fieldValue
+ });
+ }
+
+ var systemState = JsonSerializer.Serialize(item.Fields["System.State"]);
+ if (_config.OnDuplicate.SetState.ContainsKey(systemState)) {
+ document.Add(new JsonPatchOperation() {
+ Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace,
+ Path = "/fields/System.State",
+ Value = _config.OnDuplicate.SetState[systemState]
+ });
+ }
+
+ if (document.Any()) {
+ await _client.UpdateWorkItemAsync(document, _project, (int)(item.Id!));
+ _logTracer.Info($"notify ado: updated work item {item.Id} - {notificationInfo}");
+ } else {
+ _logTracer.Info($"notify ado: no update for work item {item.Id} - {notificationInfo}");
+ }
+ }
+
+ private async Async.Task> GetValidFields(string? project) {
+ return (await _client.GetFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields))
+ .Select(field => field.ReferenceName.ToLowerInvariant())
+ .ToList();
+ }
+
+ private async Async.Task CreateNew() {
+ var (taskType, document) = await RenderNew();
+ var entry = await _client.CreateWorkItemAsync(document, _project, taskType);
+
+ if (_config.Comment != null) {
+ var comment = await Render(_config.Comment);
+ await _client.AddCommentAsync(
+ new CommentCreate() {
+ Text = comment,
+ },
+ _project,
+ (int)(entry.Id!)
+ );
+ }
+ return entry;
+ }
+
+ private async Async.Task<(string, JsonPatchDocument)> RenderNew() {
+ var taskType = await Render(_config.Type);
+ var document = new JsonPatchDocument();
+ if (!_config.AdoFields.ContainsKey("System.Tags")) {
+ document.Add(new JsonPatchOperation() {
+ Operation = VisualStudio.Services.WebApi.Patch.Operation.Add,
+ Path = "/fields/System.Tags",
+ Value = "Onefuzz"
+ });
+ }
+
+ foreach (var field in _config.AdoFields.Keys) {
+ var value = await Render(_config.AdoFields[field]);
+
+ if (string.Equals(field, "System.Tags")) {
+ value += ";Onefuzz";
+ }
+
+ document.Add(new JsonPatchOperation() {
+ Operation = VisualStudio.Services.WebApi.Patch.Operation.Add,
+ Path = $"/fields/{field}",
+ Value = value
+ });
+ }
+
+ return (taskType, document);
+ }
+
+ public async Async.Task Process(string notificationInfo) {
+ var seen = false;
+ await foreach (var workItem in ExistingWorkItems()) {
+ await UpdateExisting(workItem, notificationInfo);
+ seen = true;
+ }
+
+ if (!seen) {
+ var entry = await CreateNew();
+ _logTracer.Info($"notify ado: created new work item {entry.Id} - {notificationInfo}");
+ }
+ }
+ }
+}
diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs b/src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs
new file mode 100644
index 000000000..3ae188343
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs
@@ -0,0 +1,160 @@
+using Octokit;
+
+namespace Microsoft.OneFuzz.Service;
+
+public interface IGithubIssues {
+ Async.Task GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport? reportable);
+}
+
+public class GithubIssues : NotificationsBase, IGithubIssues {
+
+ public GithubIssues(ILogTracer logTracer, IOnefuzzContext context)
+ : base(logTracer, context) { }
+
+ public async Async.Task GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport? reportable) {
+ if (reportable == null) {
+ return;
+ }
+
+ if (reportable is RegressionReport) {
+ _logTracer.Info($"github issue integration does not support regression reports. container:{container} filename:{filename}");
+ return;
+ }
+
+ var report = (Report)reportable;
+
+ try {
+ await Process(config, container, filename, report);
+ } catch (ApiException e) {
+ await FailTask(report, e);
+ }
+ }
+
+ private async Async.Task Process(GithubIssuesTemplate config, Container container, string filename, Report report) {
+ var renderer = await Renderer.ConstructRenderer(_context, container, filename, report);
+ var handler = await GithubConnnector.GithubConnnectorCreator(config, container, filename, renderer, _context.Creds.GetInstanceUrl(), _context, _logTracer);
+ await handler.Process();
+ }
+ class GithubConnnector {
+ private readonly GitHubClient _gh;
+ private readonly GithubIssuesTemplate _config;
+ private readonly Renderer _renderer;
+ private readonly Uri _instanceUrl;
+ private readonly ILogTracer _logTracer;
+
+ public static async Async.Task GithubConnnectorCreator(GithubIssuesTemplate config, Container container, string filename, Renderer renderer, Uri instanceUrl, IOnefuzzContext context, ILogTracer logTracer) {
+ var auth = config.Auth.Secret switch {
+ SecretAddress sa => await context.SecretsOperations.GetSecretObj(sa.Url),
+ SecretValue sv => sv.Value,
+ _ => throw new ArgumentException($"Unexpected secret type {config.Auth.Secret.GetType()}")
+ };
+ return new GithubConnnector(config, container, filename, renderer, instanceUrl, auth!, logTracer);
+ }
+
+ public GithubConnnector(GithubIssuesTemplate config, Container container, string filename, Renderer renderer, Uri instanceUrl, GithubAuth auth, ILogTracer logTracer) {
+ _config = config;
+ _gh = new GitHubClient(new ProductHeaderValue("microsoft/OneFuzz")) {
+ Credentials = new Credentials(auth.User, auth.PersonalAccessToken)
+ };
+ _renderer = renderer;
+ _instanceUrl = instanceUrl;
+ _logTracer = logTracer;
+ }
+
+ public async Async.Task Process() {
+ var issues = await Existing();
+ if (issues.Any()) {
+ await Update(issues.First());
+ } else {
+ await Create();
+ }
+ }
+
+ private async Async.Task Render(string field) {
+ return await _renderer.Render(field, _instanceUrl);
+ }
+
+ private async Async.Task> Existing() {
+ var query = new List() {
+ await Render(_config.UniqueSearch.str),
+ $"repo:{_config.Organization}/{_config.Repository}"
+ };
+
+ if (_config.UniqueSearch.Author != null) {
+ query.Add($"author:{await Render(_config.UniqueSearch.Author)}");
+ }
+
+ if (_config.UniqueSearch.State != null) {
+ query.Add($"state:{_config.UniqueSearch.State}");
+ }
+
+ var title = await Render(_config.Title);
+ var body = await Render(_config.Body);
+ var issues = new List();
+ var t = await _gh.Search.SearchIssues(new SearchIssuesRequest(string.Join(' ', query)));
+ foreach (var issue in t.Items) {
+ var skip = false;
+ foreach (var field in _config.UniqueSearch.FieldMatch) {
+ if (field == GithubIssueSearchMatch.Title && issue.Title != title) {
+ skip = true;
+ break;
+ }
+ if (field == GithubIssueSearchMatch.Body && issue.Body != body) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ issues.Add(issue);
+ }
+ }
+ return issues;
+ }
+
+ private async Async.Task Update(Issue issue) {
+ _logTracer.Info($"updating issue: {issue}");
+ if (_config.OnDuplicate.Comment != null) {
+ await _gh.Issue.Comment.Create(issue.Repository.Id, issue.Number, await Render(_config.OnDuplicate.Comment));
+ }
+ if (_config.OnDuplicate.Labels.Any()) {
+ var labels = await _config.OnDuplicate.Labels.ToAsyncEnumerable()
+ .SelectAwait(async label => await Render(label))
+ .ToArrayAsync();
+
+ await _gh.Issue.Labels.ReplaceAllForIssue(issue.Repository.Id, issue.Number, labels);
+ }
+ if (_config.OnDuplicate.Reopen && issue.State != ItemState.Open) {
+ await _gh.Issue.Update(issue.Repository.Id, issue.Number, new IssueUpdate() {
+ State = ItemState.Open
+ });
+ }
+ }
+
+ private async Async.Task Create() {
+ _logTracer.Info($"creating issue");
+ var assignees = await _config.Assignees.ToAsyncEnumerable()
+ .SelectAwait(async assignee => await Render(assignee))
+ .ToListAsync();
+
+ var labels = await _config.Labels.ToAsyncEnumerable()
+ .SelectAwait(async label => await Render(label))
+ .ToHashSetAsync();
+
+ labels.Add("OneFuzz");
+
+ var newIssue = new NewIssue(await Render(_config.Title)) {
+ Body = await Render(_config.Body),
+ };
+
+ labels.ToList().ForEach(label => newIssue.Labels.Add(label));
+ assignees.ForEach(assignee => newIssue.Assignees.Add(assignee));
+
+ await _gh.Issue.Create(
+ await Render(_config.Organization),
+ await Render(_config.Repository),
+ newIssue
+ );
+ }
+ }
+}
+
diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs b/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs
new file mode 100644
index 000000000..8f83cf43e
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs
@@ -0,0 +1,99 @@
+using Scriban;
+
+namespace Microsoft.OneFuzz.Service;
+
+public abstract class NotificationsBase {
+
+#pragma warning disable CA1051 // permit visible instance fields
+ protected readonly ILogTracer _logTracer;
+ protected readonly IOnefuzzContext _context;
+#pragma warning restore CA1051
+ public NotificationsBase(ILogTracer logTracer, IOnefuzzContext context) {
+ _logTracer = logTracer;
+ _context = context;
+ }
+
+ public async Async.Task FailTask(Report report, Exception error) {
+ _logTracer.Error($"notification failed: job_id:{report.JobId} task_id:{report.TaskId} err:{error}");
+
+ var task = await _context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
+ if (task != null) {
+ await _context.TaskOperations.MarkFailed(task, new Error(ErrorCode.NOTIFICATION_FAILURE, new string[] {
+ "notification failed",
+ error.ToString()
+ }));
+ }
+ }
+
+ public static string ReplaceFirstSetup(string executable) {
+ var setup = "setup/";
+ int index = executable.IndexOf(setup, StringComparison.InvariantCultureIgnoreCase);
+ var setupFileName = (index < 0) ? executable : executable.Remove(index, setup.Length);
+ return setupFileName;
+ }
+
+ protected class Renderer {
+ private readonly Report _report;
+ private readonly Container _container;
+ private readonly string _filename;
+ private readonly TaskConfig _taskConfig;
+ private readonly JobConfig _jobConfig;
+ private readonly Uri _targetUrl;
+ private readonly Uri _inputUrl;
+ private readonly Uri _reportUrl;
+
+ public static async Async.Task ConstructRenderer(IOnefuzzContext context, Container container, string filename, Report report, Task? task = null, Job? job = null, Uri? targetUrl = null, Uri? inputUrl = null, Uri? reportUrl = null) {
+ task ??= await context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
+ task.EnsureNotNull($"invalid task {report.TaskId}");
+
+ job ??= await context.JobOperations.Get(report.JobId);
+ job.EnsureNotNull($"invalid job {report.JobId}");
+
+ if (targetUrl == null) {
+ var setupContainer = Scheduler.GetSetupContainer(task?.Config!);
+ targetUrl = new Uri(context.Containers.AuthDownloadUrl(setupContainer, ReplaceFirstSetup(report.Executable)));
+ }
+
+ if (reportUrl == null) {
+ reportUrl = new Uri(context.Containers.AuthDownloadUrl(container, filename));
+ }
+
+ if (inputUrl == null && report.InputBlob != null) {
+ inputUrl = new Uri(context.Containers.AuthDownloadUrl(report.InputBlob.Container, report.InputBlob.Name));
+ }
+
+ return new Renderer(container, filename, report, task!, job!, targetUrl, inputUrl!, reportUrl);
+ }
+ public Renderer(Container container, string filename, Report report, Task task, Job job, Uri targetUrl, Uri inputUrl, Uri reportUrl) {
+ _report = report;
+ _container = container;
+ _filename = filename;
+ _taskConfig = task.Config;
+ _jobConfig = job.Config;
+ _reportUrl = reportUrl;
+ _targetUrl = targetUrl;
+ _inputUrl = inputUrl;
+ }
+
+ // TODO: This function is fallible but the python
+ // implementation doesn't have that so I'm trying to match it.
+ // We should probably propagate any errors up
+ public async Async.Task Render(string templateString, Uri instanceUrl) {
+ var template = Template.Parse(templateString);
+ if (template != null) {
+ return await template.RenderAsync(new {
+ Report = this._report,
+ Task = this._taskConfig,
+ Job = this._jobConfig,
+ ReportUrl = this._reportUrl,
+ InputUrl = _inputUrl,
+ TargetUrl = _targetUrl,
+ ReportContainer = _container,
+ ReportFilename = _filename,
+ ReproCmd = $"onefuzz --endpoint {instanceUrl} repro create_and_connect {_container} {_filename}"
+ });
+ }
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs
new file mode 100644
index 000000000..c983541f8
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs
@@ -0,0 +1,124 @@
+using System.Net.Http;
+using System.Text.Json;
+
+namespace Microsoft.OneFuzz.Service;
+
+public interface ITeams {
+ Async.Task NotifyTeams(TeamsTemplate config, Container container, string filename, IReport reportOrRegression);
+}
+
+public class Teams : ITeams {
+ private readonly ILogTracer _logTracer;
+ private readonly IOnefuzzContext _context;
+ private readonly IHttpClientFactory _httpFactory;
+
+ public Teams(IHttpClientFactory httpFactory, ILogTracer logTracer, IOnefuzzContext context) {
+ _logTracer = logTracer;
+ _context = context;
+ _httpFactory = httpFactory;
+ }
+
+ private static string CodeBlock(string data) {
+ data = data.Replace("`", "``");
+ return $"\n```\n{data}\n```\n";
+ }
+
+ private async Async.Task SendTeamsWebhook(TeamsTemplate config, string title, IList> facts, string? text) {
+ title = MarkdownEscape(title);
+
+ var sections = new List>() {
+ new() {
+ {"activityTitle", title},
+ {"facts", facts}
+ }
+ };
+ if (text != null) {
+ sections.Add(new() {
+ { "text", text }
+ });
+ }
+
+ var message = new Dictionary() {
+ {"@type", "MessageCard"},
+ {"@context", "https://schema.org/extensions"},
+ {"summary", title},
+ {"sections", sections}
+ };
+
+ var configUrl = await _context.SecretsOperations.GetSecretStringValue(config.Url);
+ var client = new Request(_httpFactory.CreateClient());
+ var response = await client.Post(url: new Uri(configUrl!), JsonSerializer.Serialize(message));
+ if (response == null || !response.IsSuccessStatusCode) {
+ _logTracer.Error($"webhook failed {response?.StatusCode} {response?.Content}");
+ }
+ }
+
+ public async Async.Task NotifyTeams(TeamsTemplate config, Container container, string filename, IReport reportOrRegression) {
+ var facts = new List>();
+ string? text = null;
+ var title = string.Empty;
+
+ if (reportOrRegression is Report report) {
+ var task = await _context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
+ if (task == null) {
+ _logTracer.Error($"report with invalid task {report.JobId}:{report.TaskId}");
+ return;
+ }
+
+ title = $"new crash in {report.Executable}: {report.CrashType} @ {report.CrashSite}";
+
+ var links = new List {
+ $"[report]({_context.Containers.AuthDownloadUrl(container, filename)})"
+ };
+
+ var setupContainer = Scheduler.GetSetupContainer(task.Config);
+ if (setupContainer != null) {
+ var setupFileName = NotificationsBase.ReplaceFirstSetup(report.Executable);
+ links.Add(
+ $"[executable]({_context.Containers.AuthDownloadUrl(setupContainer, setupFileName)})"
+ );
+ }
+
+ if (report.InputBlob != null) {
+ links.Add(
+ $"[input]({_context.Containers.AuthDownloadUrl(report.InputBlob.Container, report.InputBlob.Name)})"
+ );
+ }
+
+ facts.AddRange(new List> {
+ new() {{"name", "Files"}, {"value", string.Join(" | ", links)}},
+ new() {
+ {"name", "Task"},
+ {"value", MarkdownEscape(
+ $"job_id: {report.JobId} task_id: {report.TaskId}"
+ )}
+ },
+ new() {
+ {"name", "Repro"},
+ {"value", CodeBlock($"onefuzz repro create_and_connect {container} {filename}")}
+ }
+ });
+
+ text = "## Call Stack\n" + string.Join("\n", report.CallStack.Select(cs => CodeBlock(cs)));
+ } else {
+ title = "new file found";
+ var fileUrl = _context.Containers.AuthDownloadUrl(container, filename);
+
+ facts.Add(new Dictionary() {
+ {"name", "file"},
+ {"value", $"[{MarkdownEscape(container.String)}/{MarkdownEscape(filename)}]({fileUrl})"}
+ });
+ }
+
+ await SendTeamsWebhook(config, title, facts, text);
+ }
+
+ private static string MarkdownEscape(string data) {
+ var values = "\\*_{}[]()#+-.!";
+ foreach (var c in values) {
+ data = data.Replace(c.ToString(), "\\" + c);
+ }
+ data = data.Replace("`", "``");
+ return data;
+ }
+}
diff --git a/src/ApiService/ApiService/packages.lock.json b/src/ApiService/ApiService/packages.lock.json
index d03c5f2e9..198f430bd 100644
--- a/src/ApiService/ApiService/packages.lock.json
+++ b/src/ApiService/ApiService/packages.lock.json
@@ -302,6 +302,25 @@
"System.Text.Encodings.Web": "5.0.1"
}
},
+ "Microsoft.TeamFoundationServer.Client": {
+ "type": "Direct",
+ "requested": "[19.209.0-preview, )",
+ "resolved": "19.209.0-preview",
+ "contentHash": "dglVgITWfsps8pWA//2mBGVt/keD3UGdAVBXd50k9nVZiThUwWnaAoUzRf4fay/avLGXdvfkz6x9dBf6zGtfxg==",
+ "dependencies": {
+ "Microsoft.AspNet.WebApi.Client": "5.2.7",
+ "Microsoft.TeamFoundation.DistributedTask.Common.Contracts": "[19.209.0-preview]",
+ "Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]",
+ "Newtonsoft.Json": "12.0.3",
+ "System.ComponentModel.Annotations": "4.4.1"
+ }
+ },
+ "Octokit": {
+ "type": "Direct",
+ "requested": "[2.0.1, )",
+ "resolved": "2.0.1",
+ "contentHash": "JVlfUY+sfItl6RSyVKDJTutuy28cDydUwKKfzcelwNMor2Sa18pYVKna6phO8lug1b+ep+pcuFh/FPayuImsQw=="
+ },
"Scriban": {
"type": "Direct",
"requested": "[5.5.0, )",
@@ -399,6 +418,15 @@
"System.Diagnostics.DiagnosticSource": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "Transitive",
+ "resolved": "5.2.7",
+ "contentHash": "/76fAHknzvFqbznS6Uj2sOyE9rJB3PltY+f53TH8dX9RiGhk02EhuFCWljSj5nnqKaTsmma8DFR50OGyQ4yJ1g==",
+ "dependencies": {
+ "Newtonsoft.Json": "10.0.1",
+ "Newtonsoft.Json.Bson": "1.0.1"
+ }
+ },
"Microsoft.AspNetCore.Cryptography.Internal": {
"type": "Transitive",
"resolved": "5.0.8",
@@ -869,6 +897,30 @@
"Newtonsoft.Json": "10.0.3"
}
},
+ "Microsoft.TeamFoundation.DistributedTask.Common.Contracts": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "32lLZPU8pZg+mVfA2smHso6fhWPSFXJPPyawvOFsFoNz9Yj5y2fsAR7O4zPwE3c/z2zzi8BMfiXRKOcbW6cdIg==",
+ "dependencies": {
+ "Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]"
+ }
+ },
+ "Microsoft.VisualStudio.Services.Client": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "eQWZb5BhtOgywARvfHGGZsYuuZvFmJiXyE7P/EqKTLUplrUFmSVxo0J/KUC8GWJWmdarxH2vXZTAz9uW7BwRDQ==",
+ "dependencies": {
+ "Microsoft.AspNet.WebApi.Client": "5.2.7",
+ "Newtonsoft.Json": "12.0.3",
+ "System.Configuration.ConfigurationManager": "4.4.1",
+ "System.Data.SqlClient": "4.4.2",
+ "System.Security.Cryptography.Cng": "4.4.0",
+ "System.Security.Cryptography.OpenSsl": "4.4.0",
+ "System.Security.Cryptography.ProtectedData": "4.4.0",
+ "System.Security.Principal.Windows": "4.4.1",
+ "System.Xml.XPath.XmlDocument": "4.3.0"
+ }
+ },
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -949,15 +1001,16 @@
},
"Newtonsoft.Json": {
"type": "Transitive",
- "resolved": "10.0.3",
- "contentHash": "hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==",
+ "resolved": "12.0.3",
+ "contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.1",
+ "contentHash": "5PYT/IqQ+UK31AmZiSS102R6EsTo+LGTSI8bp7WAUqDKaF4wHXD8U9u4WxTI1vc64tYi++8p3dk3WWNqPFgldw==",
"dependencies": {
- "Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.Runtime.Serialization.Formatters": "4.3.0",
- "System.Runtime.Serialization.Primitives": "4.3.0",
- "System.Xml.XmlDocument": "4.3.0"
+ "Newtonsoft.Json": "10.0.1"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
@@ -984,6 +1037,16 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
+ "runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==",
+ "dependencies": {
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1067,6 +1130,21 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
+ },
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
+ },
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
+ },
"System.AppContext": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1114,71 +1192,17 @@
"System.Threading.Tasks": "4.3.0"
}
},
- "System.Collections.NonGeneric": {
+ "System.ComponentModel.Annotations": {
"type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==",
- "dependencies": {
- "System.Diagnostics.Debug": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
+ "resolved": "4.4.1",
+ "contentHash": "ToiYqSCioqhtspq2O/jYKtyTC/T0uwWHBTYlzCi6PRbSSHArN1IaRWeHffDamvms5sye5FDUWCfNZgubQpNRsA=="
},
- "System.Collections.Specialized": {
+ "System.Configuration.ConfigurationManager": {
"type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==",
+ "resolved": "4.4.1",
+ "contentHash": "jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"dependencies": {
- "System.Collections.NonGeneric": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Globalization.Extensions": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.ComponentModel": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==",
- "dependencies": {
- "System.Runtime": "4.3.0"
- }
- },
- "System.ComponentModel.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
- "dependencies": {
- "System.ComponentModel": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.ComponentModel.TypeConverter": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Collections.NonGeneric": "4.3.0",
- "System.Collections.Specialized": "4.3.0",
- "System.ComponentModel": "4.3.0",
- "System.ComponentModel.Primitives": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Linq": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Reflection.Extensions": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Reflection.TypeExtensions": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
+ "System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"System.Console": {
@@ -1193,6 +1217,17 @@
"System.Text.Encoding": "4.3.0"
}
},
+ "System.Data.SqlClient": {
+ "type": "Transitive",
+ "resolved": "4.4.2",
+ "contentHash": "Bv5J2EBAdP7FSgehKYN4O6iw1AaZrw4rFFqwt9vZSjRvC70FpwP2d9UG4aTaI2wh3vfrBKK+tjewowGM2Y6c1w==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.4.0",
+ "System.Security.Principal.Windows": "4.4.0",
+ "System.Text.Encoding.CodePages": "4.4.0",
+ "runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1646,27 +1681,6 @@
"System.Runtime.Extensions": "4.3.0"
}
},
- "System.Runtime.Serialization.Formatters": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Serialization.Primitives": "4.3.0"
- }
- },
- "System.Runtime.Serialization.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
- "dependencies": {
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -1760,22 +1774,10 @@
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "resolved": "4.4.0",
+ "contentHash": "is11qLXIHKIvbTipyB1an8FT1ZKavmgf/qJUSIz7ZP830ALRRhPSt5NhplW0/wMk0tNDQWQLluVap6HsQN4HMg==",
"dependencies": {
- "System.Collections": "4.3.0",
- "System.IO": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Runtime.Handles": "4.3.0",
- "System.Runtime.InteropServices": "4.3.0",
- "System.Runtime.Numerics": "4.3.0",
- "System.Security.Cryptography.Algorithms": "4.3.0",
- "System.Security.Cryptography.Encoding": "4.3.0",
- "System.Security.Cryptography.Primitives": "4.3.0",
- "System.Text.Encoding": "4.3.0",
- "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ "Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
@@ -1879,6 +1881,14 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "2.0.0"
+ }
+ },
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -2014,6 +2024,39 @@
"System.Threading": "4.3.0",
"System.Xml.ReaderWriter": "4.3.0"
}
+ },
+ "System.Xml.XPath": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0"
+ }
+ },
+ "System.Xml.XPath.XmlDocument": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0",
+ "System.Xml.XPath": "4.3.0",
+ "System.Xml.XmlDocument": "4.3.0"
+ }
}
}
}
diff --git a/src/ApiService/IntegrationTests/Fakes/TestContext.cs b/src/ApiService/IntegrationTests/Fakes/TestContext.cs
index bfcd778bd..60d2af0ca 100644
--- a/src/ApiService/IntegrationTests/Fakes/TestContext.cs
+++ b/src/ApiService/IntegrationTests/Fakes/TestContext.cs
@@ -117,6 +117,9 @@ public sealed class TestContext : IOnefuzzContext {
public ISubnet Subnet => throw new NotImplementedException();
public IImageOperations ImageOperations => throw new NotImplementedException();
+ public ITeams Teams => throw new NotImplementedException();
+ public IGithubIssues GithubIssues => throw new NotImplementedException();
+ public IAdo Ado => throw new NotImplementedException();
}
diff --git a/src/ApiService/IntegrationTests/packages.lock.json b/src/ApiService/IntegrationTests/packages.lock.json
index 2d61f054f..3d843d602 100644
--- a/src/ApiService/IntegrationTests/packages.lock.json
+++ b/src/ApiService/IntegrationTests/packages.lock.json
@@ -281,6 +281,15 @@
"System.Diagnostics.DiagnosticSource": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "Transitive",
+ "resolved": "5.2.7",
+ "contentHash": "/76fAHknzvFqbznS6Uj2sOyE9rJB3PltY+f53TH8dX9RiGhk02EhuFCWljSj5nnqKaTsmma8DFR50OGyQ4yJ1g==",
+ "dependencies": {
+ "Newtonsoft.Json": "10.0.1",
+ "Newtonsoft.Json.Bson": "1.0.1"
+ }
+ },
"Microsoft.AspNetCore.Cryptography.Internal": {
"type": "Transitive",
"resolved": "5.0.8",
@@ -878,6 +887,26 @@
"Newtonsoft.Json": "10.0.3"
}
},
+ "Microsoft.TeamFoundation.DistributedTask.Common.Contracts": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "32lLZPU8pZg+mVfA2smHso6fhWPSFXJPPyawvOFsFoNz9Yj5y2fsAR7O4zPwE3c/z2zzi8BMfiXRKOcbW6cdIg==",
+ "dependencies": {
+ "Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]"
+ }
+ },
+ "Microsoft.TeamFoundationServer.Client": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "dglVgITWfsps8pWA//2mBGVt/keD3UGdAVBXd50k9nVZiThUwWnaAoUzRf4fay/avLGXdvfkz6x9dBf6zGtfxg==",
+ "dependencies": {
+ "Microsoft.AspNet.WebApi.Client": "5.2.7",
+ "Microsoft.TeamFoundation.DistributedTask.Common.Contracts": "[19.209.0-preview]",
+ "Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]",
+ "Newtonsoft.Json": "12.0.3",
+ "System.ComponentModel.Annotations": "4.4.1"
+ }
+ },
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.1.0",
@@ -896,6 +925,22 @@
"Newtonsoft.Json": "9.0.1"
}
},
+ "Microsoft.VisualStudio.Services.Client": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "eQWZb5BhtOgywARvfHGGZsYuuZvFmJiXyE7P/EqKTLUplrUFmSVxo0J/KUC8GWJWmdarxH2vXZTAz9uW7BwRDQ==",
+ "dependencies": {
+ "Microsoft.AspNet.WebApi.Client": "5.2.7",
+ "Newtonsoft.Json": "12.0.3",
+ "System.Configuration.ConfigurationManager": "4.4.1",
+ "System.Data.SqlClient": "4.4.2",
+ "System.Security.Cryptography.Cng": "4.4.0",
+ "System.Security.Cryptography.OpenSsl": "4.4.0",
+ "System.Security.Cryptography.ProtectedData": "4.4.0",
+ "System.Security.Principal.Windows": "4.4.1",
+ "System.Xml.XPath.XmlDocument": "4.3.0"
+ }
+ },
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -976,15 +1021,16 @@
},
"Newtonsoft.Json": {
"type": "Transitive",
- "resolved": "10.0.3",
- "contentHash": "hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==",
+ "resolved": "12.0.3",
+ "contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.1",
+ "contentHash": "5PYT/IqQ+UK31AmZiSS102R6EsTo+LGTSI8bp7WAUqDKaF4wHXD8U9u4WxTI1vc64tYi++8p3dk3WWNqPFgldw==",
"dependencies": {
- "Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.Runtime.Serialization.Formatters": "4.3.0",
- "System.Runtime.Serialization.Primitives": "4.3.0",
- "System.Xml.XmlDocument": "4.3.0"
+ "Newtonsoft.Json": "10.0.1"
}
},
"NuGet.Frameworks": {
@@ -992,6 +1038,11 @@
"resolved": "5.11.0",
"contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q=="
},
+ "Octokit": {
+ "type": "Transitive",
+ "resolved": "2.0.1",
+ "contentHash": "JVlfUY+sfItl6RSyVKDJTutuy28cDydUwKKfzcelwNMor2Sa18pYVKna6phO8lug1b+ep+pcuFh/FPayuImsQw=="
+ },
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.2",
@@ -1016,6 +1067,16 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
+ "runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==",
+ "dependencies": {
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1099,6 +1160,21 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
+ },
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
+ },
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
+ },
"Scriban": {
"type": "Transitive",
"resolved": "5.5.0",
@@ -1191,6 +1267,11 @@
"System.Runtime": "4.3.0"
}
},
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "4.4.1",
+ "contentHash": "ToiYqSCioqhtspq2O/jYKtyTC/T0uwWHBTYlzCi6PRbSSHArN1IaRWeHffDamvms5sye5FDUWCfNZgubQpNRsA=="
+ },
"System.ComponentModel.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1225,8 +1306,8 @@
},
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==",
+ "resolved": "4.4.1",
+ "contentHash": "jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"dependencies": {
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
@@ -1243,6 +1324,17 @@
"System.Text.Encoding": "4.3.0"
}
},
+ "System.Data.SqlClient": {
+ "type": "Transitive",
+ "resolved": "4.4.2",
+ "contentHash": "Bv5J2EBAdP7FSgehKYN4O6iw1AaZrw4rFFqwt9vZSjRvC70FpwP2d9UG4aTaI2wh3vfrBKK+tjewowGM2Y6c1w==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.4.0",
+ "System.Security.Principal.Windows": "4.4.0",
+ "System.Text.Encoding.CodePages": "4.4.0",
+ "runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1755,27 +1847,6 @@
"System.Runtime.Extensions": "4.3.0"
}
},
- "System.Runtime.Serialization.Formatters": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Serialization.Primitives": "4.3.0"
- }
- },
- "System.Runtime.Serialization.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
- "dependencies": {
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -1869,22 +1940,10 @@
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "resolved": "4.4.0",
+ "contentHash": "is11qLXIHKIvbTipyB1an8FT1ZKavmgf/qJUSIz7ZP830ALRRhPSt5NhplW0/wMk0tNDQWQLluVap6HsQN4HMg==",
"dependencies": {
- "System.Collections": "4.3.0",
- "System.IO": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Runtime.Handles": "4.3.0",
- "System.Runtime.InteropServices": "4.3.0",
- "System.Runtime.Numerics": "4.3.0",
- "System.Security.Cryptography.Algorithms": "4.3.0",
- "System.Security.Cryptography.Encoding": "4.3.0",
- "System.Security.Cryptography.Primitives": "4.3.0",
- "System.Text.Encoding": "4.3.0",
- "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ "Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
@@ -1988,6 +2047,14 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "2.0.0"
+ }
+ },
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -2124,6 +2191,39 @@
"System.Xml.ReaderWriter": "4.3.0"
}
},
+ "System.Xml.XPath": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0"
+ }
+ },
+ "System.Xml.XPath.XmlDocument": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0",
+ "System.Xml.XPath": "4.3.0",
+ "System.Xml.XmlDocument": "4.3.0"
+ }
+ },
"TaskTupleAwaiter": {
"type": "Transitive",
"resolved": "2.0.0",
@@ -2177,39 +2277,41 @@
"apiservice": {
"type": "Project",
"dependencies": {
- "Azure.Core": "[1.25.0, )",
- "Azure.Data.Tables": "[12.5.0, )",
- "Azure.Identity": "[1.6.0, )",
- "Azure.Messaging.EventGrid": "[4.10.0, )",
- "Azure.ResourceManager": "[1.3.1, )",
- "Azure.ResourceManager.Compute": "[1.0.0-beta.8, )",
- "Azure.ResourceManager.Monitor": "[1.0.0-beta.2, )",
- "Azure.ResourceManager.Network": "[1.0.0, )",
- "Azure.ResourceManager.Resources": "[1.3.0, )",
- "Azure.ResourceManager.Storage": "[1.0.0-beta.11, )",
- "Azure.Security.KeyVault.Secrets": "[4.3.0, )",
- "Azure.Storage.Blobs": "[12.13.0, )",
- "Azure.Storage.Queues": "[12.11.0, )",
- "Faithlife.Utility": "[0.12.2, )",
- "Microsoft.ApplicationInsights.DependencyCollector": "[2.21.0, )",
- "Microsoft.Azure.Functions.Worker": "[1.6.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "[2.1.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.Http": "[3.0.13, )",
- "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "[1.7.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.Storage": "[5.0.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.Timer": "[4.1.0, )",
- "Microsoft.Azure.Functions.Worker.Sdk": "[1.3.0, )",
- "Microsoft.Azure.Management.Monitor": "[0.28.0-preview, )",
- "Microsoft.Azure.Management.OperationalInsights": "[0.24.0-preview, )",
- "Microsoft.Extensions.Logging.ApplicationInsights": "[2.21.0, )",
- "Microsoft.Graph": "[4.37.0, )",
- "Microsoft.Identity.Client": "[4.46.2, )",
- "Microsoft.Identity.Web.TokenCache": "[1.23.1, )",
- "Scriban": "[5.5.0, )",
- "Semver": "[2.1.0, )",
- "System.IdentityModel.Tokens.Jwt": "[6.22.1, )",
- "System.Linq.Async": "[6.0.1, )",
- "TaskTupleAwaiter": "[2.0.0, )"
+ "Azure.Core": "1.25.0",
+ "Azure.Data.Tables": "12.5.0",
+ "Azure.Identity": "1.6.0",
+ "Azure.Messaging.EventGrid": "4.10.0",
+ "Azure.ResourceManager": "1.3.1",
+ "Azure.ResourceManager.Compute": "1.0.0-beta.8",
+ "Azure.ResourceManager.Monitor": "1.0.0-beta.2",
+ "Azure.ResourceManager.Network": "1.0.0",
+ "Azure.ResourceManager.Resources": "1.3.0",
+ "Azure.ResourceManager.Storage": "1.0.0-beta.11",
+ "Azure.Security.KeyVault.Secrets": "4.3.0",
+ "Azure.Storage.Blobs": "12.13.0",
+ "Azure.Storage.Queues": "12.11.0",
+ "Faithlife.Utility": "0.12.2",
+ "Microsoft.ApplicationInsights.DependencyCollector": "2.21.0",
+ "Microsoft.Azure.Functions.Worker": "1.6.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "2.1.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.13",
+ "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "1.7.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.Storage": "5.0.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.Timer": "4.1.0",
+ "Microsoft.Azure.Functions.Worker.Sdk": "1.3.0",
+ "Microsoft.Azure.Management.Monitor": "0.28.0-preview",
+ "Microsoft.Azure.Management.OperationalInsights": "0.24.0-preview",
+ "Microsoft.Extensions.Logging.ApplicationInsights": "2.21.0",
+ "Microsoft.Graph": "4.37.0",
+ "Microsoft.Identity.Client": "4.46.2",
+ "Microsoft.Identity.Web.TokenCache": "1.23.1",
+ "Microsoft.TeamFoundationServer.Client": "19.209.0-preview",
+ "Octokit": "2.0.1",
+ "Scriban": "5.5.0",
+ "Semver": "2.1.0",
+ "System.IdentityModel.Tokens.Jwt": "6.22.1",
+ "System.Linq.Async": "6.0.1",
+ "TaskTupleAwaiter": "2.0.0"
}
}
}
diff --git a/src/ApiService/Tests/OrmModelsTest.cs b/src/ApiService/Tests/OrmModelsTest.cs
index 3dbc03cbe..0c8de3617 100644
--- a/src/ApiService/Tests/OrmModelsTest.cs
+++ b/src/ApiService/Tests/OrmModelsTest.cs
@@ -368,11 +368,87 @@ namespace Tests {
where Container.TryParse(nameString, out var _)
select Container.Parse(nameString);
+ public static Gen AdoDuplicateTemplate() {
+ return Arb.Generate, Dictionary, string?>>().Select(
+ arg =>
+ new ADODuplicateTemplate(
+ arg.Item1,
+ arg.Item2,
+ arg.Item2,
+ arg.Item3
+ )
+ );
+ }
+
+ public static Gen AdoTemplate() {
+ return Arb.Generate, NonEmptyString, List, Dictionary, ADODuplicateTemplate, string?>>().Select(
+ arg =>
+ new AdoTemplate(
+ arg.Item1,
+ arg.Item2,
+ arg.Item3.Item,
+ arg.Item3.Item,
+ arg.Item4,
+ arg.Item5,
+ AdoDuplicateTemplate().Sample(1, 1).First(),
+ arg.Item7
+ )
+ );
+ }
+
+ public static Gen TeamsTemplate() {
+ return Arb.Generate>>().Select(
+ arg =>
+ new TeamsTemplate(
+ arg.Item1
+ )
+ );
+ }
+
+ public static Gen GithubAuth() {
+ return Arb.Generate>().Select(
+ arg =>
+ new GithubAuth(
+ arg.Item1,
+ arg.Item1
+ )
+ );
+ }
+
+ public static Gen GithubIssueSearch() {
+ return Arb.Generate, string, string?, GithubIssueState?>>().Select(
+ arg =>
+ new GithubIssueSearch(
+ arg.Item1,
+ arg.Item2,
+ arg.Item3,
+ arg.Item4
+ )
+ );
+ }
+
+ public static Gen GithubIssuesTemplate() {
+ return Arb.Generate, NonEmptyString, GithubIssueSearch, List, GithubIssueDuplicate>>().Select(
+ arg =>
+ new GithubIssuesTemplate(
+ arg.Item1,
+ arg.Item2.Item,
+ arg.Item2.Item,
+ arg.Item2.Item,
+ arg.Item2.Item,
+ arg.Item3,
+ arg.Item4,
+ arg.Item4,
+ arg.Item5
+ )
+ );
+ }
+
public static Gen NotificationTemplate() {
return Gen.OneOf(new[] {
- Arb.Generate().Select(e => e as NotificationTemplate),
- Arb.Generate().Select(e => e as NotificationTemplate),
- Arb.Generate().Select(e => e as NotificationTemplate)
+ AdoTemplate().Select(a => a as NotificationTemplate),
+ TeamsTemplate().Select(e => e as NotificationTemplate),
+ GithubIssuesTemplate().Select(e => e as NotificationTemplate)
});
}
@@ -1006,17 +1082,15 @@ namespace Tests {
}
- /*
+
//Sample function on how repro a failing test run, using Replay
//functionality of FsCheck. Feel free to
[Property]
- void Replay()
- {
- var seed = FsCheck.Random.StdGen.NewStdGen(4570702, 297027754);
- var p = Prop.ForAll((WebhookMessageEventGrid x) => WebhookMessageEventGrid(x) );
+ void Replay() {
+ var seed = FsCheck.Random.StdGen.NewStdGen(811038773, 297085737);
+ var p = Prop.ForAll((NotificationTemplate x) => NotificationTemplate(x));
p.Check(new Configuration { Replay = seed });
}
- */
}
}
diff --git a/src/ApiService/Tests/TemplateTests.cs b/src/ApiService/Tests/TemplateTests.cs
index 7147ebcb8..d3b07e7d2 100644
--- a/src/ApiService/Tests/TemplateTests.cs
+++ b/src/ApiService/Tests/TemplateTests.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Text.Json;
using FluentAssertions;
using Microsoft.OneFuzz.Service;
+using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using Scriban;
using Xunit;
@@ -115,6 +117,19 @@ public class TemplateTests {
output.Should().NotContainAny(report.CallStack);
}
+ [Fact]
+ public void TemplatesShouldDeserializeAppropriately() {
+ var teamsTemplate = @"{""url"": {""secret"": {""url"": ""https://example.com""}}}";
+ var template = JsonSerializer.Deserialize(teamsTemplate, EntityConverter.GetJsonSerializerOptions());
+ var a = template is AdoTemplate;
+ var t = template is TeamsTemplate;
+ var g = template is GithubIssuesTemplate;
+
+ a.Should().BeFalse();
+ t.Should().BeTrue();
+ g.Should().BeFalse();
+ }
+
private static Report GetReport() {
return new Report(
"https://example.com",
diff --git a/src/ApiService/Tests/packages.lock.json b/src/ApiService/Tests/packages.lock.json
index 587dcd926..13dcea0c2 100644
--- a/src/ApiService/Tests/packages.lock.json
+++ b/src/ApiService/Tests/packages.lock.json
@@ -330,6 +330,15 @@
"System.Diagnostics.DiagnosticSource": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "Transitive",
+ "resolved": "5.2.7",
+ "contentHash": "/76fAHknzvFqbznS6Uj2sOyE9rJB3PltY+f53TH8dX9RiGhk02EhuFCWljSj5nnqKaTsmma8DFR50OGyQ4yJ1g==",
+ "dependencies": {
+ "Newtonsoft.Json": "10.0.1",
+ "Newtonsoft.Json.Bson": "1.0.1"
+ }
+ },
"Microsoft.AspNetCore.Cryptography.Internal": {
"type": "Transitive",
"resolved": "5.0.8",
@@ -927,6 +936,26 @@
"Newtonsoft.Json": "10.0.3"
}
},
+ "Microsoft.TeamFoundation.DistributedTask.Common.Contracts": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "32lLZPU8pZg+mVfA2smHso6fhWPSFXJPPyawvOFsFoNz9Yj5y2fsAR7O4zPwE3c/z2zzi8BMfiXRKOcbW6cdIg==",
+ "dependencies": {
+ "Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]"
+ }
+ },
+ "Microsoft.TeamFoundationServer.Client": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "dglVgITWfsps8pWA//2mBGVt/keD3UGdAVBXd50k9nVZiThUwWnaAoUzRf4fay/avLGXdvfkz6x9dBf6zGtfxg==",
+ "dependencies": {
+ "Microsoft.AspNet.WebApi.Client": "5.2.7",
+ "Microsoft.TeamFoundation.DistributedTask.Common.Contracts": "[19.209.0-preview]",
+ "Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]",
+ "Newtonsoft.Json": "12.0.3",
+ "System.ComponentModel.Annotations": "4.4.1"
+ }
+ },
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.1.0",
@@ -945,6 +974,22 @@
"Newtonsoft.Json": "9.0.1"
}
},
+ "Microsoft.VisualStudio.Services.Client": {
+ "type": "Transitive",
+ "resolved": "19.209.0-preview",
+ "contentHash": "eQWZb5BhtOgywARvfHGGZsYuuZvFmJiXyE7P/EqKTLUplrUFmSVxo0J/KUC8GWJWmdarxH2vXZTAz9uW7BwRDQ==",
+ "dependencies": {
+ "Microsoft.AspNet.WebApi.Client": "5.2.7",
+ "Newtonsoft.Json": "12.0.3",
+ "System.Configuration.ConfigurationManager": "4.4.1",
+ "System.Data.SqlClient": "4.4.2",
+ "System.Security.Cryptography.Cng": "4.4.0",
+ "System.Security.Cryptography.OpenSsl": "4.4.0",
+ "System.Security.Cryptography.ProtectedData": "4.4.0",
+ "System.Security.Principal.Windows": "4.4.1",
+ "System.Xml.XPath.XmlDocument": "4.3.0"
+ }
+ },
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1025,15 +1070,16 @@
},
"Newtonsoft.Json": {
"type": "Transitive",
- "resolved": "10.0.3",
- "contentHash": "hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==",
+ "resolved": "12.0.3",
+ "contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.1",
+ "contentHash": "5PYT/IqQ+UK31AmZiSS102R6EsTo+LGTSI8bp7WAUqDKaF4wHXD8U9u4WxTI1vc64tYi++8p3dk3WWNqPFgldw==",
"dependencies": {
- "Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.Runtime.Serialization.Formatters": "4.3.0",
- "System.Runtime.Serialization.Primitives": "4.3.0",
- "System.Xml.XmlDocument": "4.3.0"
+ "Newtonsoft.Json": "10.0.1"
}
},
"NuGet.Frameworks": {
@@ -1041,6 +1087,11 @@
"resolved": "5.11.0",
"contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q=="
},
+ "Octokit": {
+ "type": "Transitive",
+ "resolved": "2.0.1",
+ "contentHash": "JVlfUY+sfItl6RSyVKDJTutuy28cDydUwKKfzcelwNMor2Sa18pYVKna6phO8lug1b+ep+pcuFh/FPayuImsQw=="
+ },
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.2",
@@ -1065,6 +1116,16 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
+ "runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==",
+ "dependencies": {
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1148,6 +1209,21 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
+ },
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
+ },
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
+ },
"Scriban": {
"type": "Transitive",
"resolved": "5.5.0",
@@ -1240,6 +1316,11 @@
"System.Runtime": "4.3.0"
}
},
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "4.4.1",
+ "contentHash": "ToiYqSCioqhtspq2O/jYKtyTC/T0uwWHBTYlzCi6PRbSSHArN1IaRWeHffDamvms5sye5FDUWCfNZgubQpNRsA=="
+ },
"System.ComponentModel.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1274,8 +1355,8 @@
},
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==",
+ "resolved": "4.4.1",
+ "contentHash": "jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"dependencies": {
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
@@ -1292,6 +1373,17 @@
"System.Text.Encoding": "4.3.0"
}
},
+ "System.Data.SqlClient": {
+ "type": "Transitive",
+ "resolved": "4.4.2",
+ "contentHash": "Bv5J2EBAdP7FSgehKYN4O6iw1AaZrw4rFFqwt9vZSjRvC70FpwP2d9UG4aTaI2wh3vfrBKK+tjewowGM2Y6c1w==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.4.0",
+ "System.Security.Principal.Windows": "4.4.0",
+ "System.Text.Encoding.CodePages": "4.4.0",
+ "runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1850,27 +1942,6 @@
"System.Runtime.Extensions": "4.3.0"
}
},
- "System.Runtime.Serialization.Formatters": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Serialization.Primitives": "4.3.0"
- }
- },
- "System.Runtime.Serialization.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
- "dependencies": {
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -1964,22 +2035,10 @@
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "resolved": "4.4.0",
+ "contentHash": "is11qLXIHKIvbTipyB1an8FT1ZKavmgf/qJUSIz7ZP830ALRRhPSt5NhplW0/wMk0tNDQWQLluVap6HsQN4HMg==",
"dependencies": {
- "System.Collections": "4.3.0",
- "System.IO": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Runtime.Handles": "4.3.0",
- "System.Runtime.InteropServices": "4.3.0",
- "System.Runtime.Numerics": "4.3.0",
- "System.Security.Cryptography.Algorithms": "4.3.0",
- "System.Security.Cryptography.Encoding": "4.3.0",
- "System.Security.Cryptography.Primitives": "4.3.0",
- "System.Text.Encoding": "4.3.0",
- "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ "Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
@@ -2083,6 +2142,14 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "2.0.0"
+ }
+ },
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -2251,6 +2318,39 @@
"System.Xml.ReaderWriter": "4.3.0"
}
},
+ "System.Xml.XPath": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0"
+ }
+ },
+ "System.Xml.XPath.XmlDocument": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0",
+ "System.Xml.XPath": "4.3.0",
+ "System.Xml.XmlDocument": "4.3.0"
+ }
+ },
"TaskTupleAwaiter": {
"type": "Transitive",
"resolved": "2.0.0",
@@ -2304,39 +2404,41 @@
"apiservice": {
"type": "Project",
"dependencies": {
- "Azure.Core": "[1.25.0, )",
- "Azure.Data.Tables": "[12.5.0, )",
- "Azure.Identity": "[1.6.0, )",
- "Azure.Messaging.EventGrid": "[4.10.0, )",
- "Azure.ResourceManager": "[1.3.1, )",
- "Azure.ResourceManager.Compute": "[1.0.0-beta.8, )",
- "Azure.ResourceManager.Monitor": "[1.0.0-beta.2, )",
- "Azure.ResourceManager.Network": "[1.0.0, )",
- "Azure.ResourceManager.Resources": "[1.3.0, )",
- "Azure.ResourceManager.Storage": "[1.0.0-beta.11, )",
- "Azure.Security.KeyVault.Secrets": "[4.3.0, )",
- "Azure.Storage.Blobs": "[12.13.0, )",
- "Azure.Storage.Queues": "[12.11.0, )",
- "Faithlife.Utility": "[0.12.2, )",
- "Microsoft.ApplicationInsights.DependencyCollector": "[2.21.0, )",
- "Microsoft.Azure.Functions.Worker": "[1.6.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "[2.1.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.Http": "[3.0.13, )",
- "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "[1.7.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.Storage": "[5.0.0, )",
- "Microsoft.Azure.Functions.Worker.Extensions.Timer": "[4.1.0, )",
- "Microsoft.Azure.Functions.Worker.Sdk": "[1.3.0, )",
- "Microsoft.Azure.Management.Monitor": "[0.28.0-preview, )",
- "Microsoft.Azure.Management.OperationalInsights": "[0.24.0-preview, )",
- "Microsoft.Extensions.Logging.ApplicationInsights": "[2.21.0, )",
- "Microsoft.Graph": "[4.37.0, )",
- "Microsoft.Identity.Client": "[4.46.2, )",
- "Microsoft.Identity.Web.TokenCache": "[1.23.1, )",
- "Scriban": "[5.5.0, )",
- "Semver": "[2.1.0, )",
- "System.IdentityModel.Tokens.Jwt": "[6.22.1, )",
- "System.Linq.Async": "[6.0.1, )",
- "TaskTupleAwaiter": "[2.0.0, )"
+ "Azure.Core": "1.25.0",
+ "Azure.Data.Tables": "12.5.0",
+ "Azure.Identity": "1.6.0",
+ "Azure.Messaging.EventGrid": "4.10.0",
+ "Azure.ResourceManager": "1.3.1",
+ "Azure.ResourceManager.Compute": "1.0.0-beta.8",
+ "Azure.ResourceManager.Monitor": "1.0.0-beta.2",
+ "Azure.ResourceManager.Network": "1.0.0",
+ "Azure.ResourceManager.Resources": "1.3.0",
+ "Azure.ResourceManager.Storage": "1.0.0-beta.11",
+ "Azure.Security.KeyVault.Secrets": "4.3.0",
+ "Azure.Storage.Blobs": "12.13.0",
+ "Azure.Storage.Queues": "12.11.0",
+ "Faithlife.Utility": "0.12.2",
+ "Microsoft.ApplicationInsights.DependencyCollector": "2.21.0",
+ "Microsoft.Azure.Functions.Worker": "1.6.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "2.1.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.13",
+ "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "1.7.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.Storage": "5.0.0",
+ "Microsoft.Azure.Functions.Worker.Extensions.Timer": "4.1.0",
+ "Microsoft.Azure.Functions.Worker.Sdk": "1.3.0",
+ "Microsoft.Azure.Management.Monitor": "0.28.0-preview",
+ "Microsoft.Azure.Management.OperationalInsights": "0.24.0-preview",
+ "Microsoft.Extensions.Logging.ApplicationInsights": "2.21.0",
+ "Microsoft.Graph": "4.37.0",
+ "Microsoft.Identity.Client": "4.46.2",
+ "Microsoft.Identity.Web.TokenCache": "1.23.1",
+ "Microsoft.TeamFoundationServer.Client": "19.209.0-preview",
+ "Octokit": "2.0.1",
+ "Scriban": "5.5.0",
+ "Semver": "2.1.0",
+ "System.IdentityModel.Tokens.Jwt": "6.22.1",
+ "System.Linq.Async": "6.0.1",
+ "TaskTupleAwaiter": "2.0.0"
}
}
}