Refactor notification support (#2363)

* 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
This commit is contained in:
Teo Voinea
2022-09-14 11:07:52 -04:00
committed by GitHub
parent f375ee719e
commit ca7b6be43b
21 changed files with 1382 additions and 330 deletions

View File

@ -42,6 +42,8 @@
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="1.7.0" />
<PackageReference Include="TaskTupleAwaiter" Version="2.0.0" />
<PackageReference Include="Scriban" Version="5.5.0" />
<PackageReference Include="Octokit" Version="2.0.1" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.209.0-preview" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">

View File

@ -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<JsonDocument>(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();

View File

@ -84,7 +84,11 @@ public record ProxyHeartbeat
Guid ProxyId,
List<Forward> 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<NotificationTemplate>
public override NotificationTemplate? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
using var templateJson = JsonDocument.ParseValue(ref reader);
try {
return templateJson.Deserialize<AdoTemplate>(options);
} catch (JsonException) {
return ValidateDeserialization(templateJson.Deserialize<AdoTemplate>(options));
} catch (Exception ex) when (
ex is JsonException
|| ex is ArgumentNullException
|| ex is ArgumentOutOfRangeException
) {
}
try {
return templateJson.Deserialize<TeamsTemplate>(options);
} catch (JsonException) {
return ValidateDeserialization(templateJson.Deserialize<TeamsTemplate>(options));
} catch (Exception ex) when (
ex is JsonException
|| ex is ArgumentNullException
|| ex is ArgumentOutOfRangeException
) {
}
try {
return templateJson.Deserialize<GithubIssuesTemplate>(options);
} catch (JsonException) {
return ValidateDeserialization(templateJson.Deserialize<GithubIssuesTemplate>(options));
} catch (Exception ex) when (
ex is JsonException
|| ex is ArgumentNullException
|| ex is ArgumentOutOfRangeException
) {
}
throw new JsonException("Unsupported notification template");
var expectedTemplateTypes = new List<Type> {
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<NotificationTemplate>
}
}
private static T ValidateDeserialization<T>(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<Endpoint>();
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<string> Increment,
string? Comment,
Dictionary<string, string> SetState,
Dictionary<string, string> AdoFields
Dictionary<string, string> AdoFields,
string? Comment = null
);
public record AdoTemplate(
@ -515,27 +565,26 @@ public record AdoTemplate(
string Project,
string Type,
List<string> UniqueFields,
string? Comment,
Dictionary<string, string> AdoFields,
ADODuplicateTemplate OnDuplicate
ADODuplicateTemplate OnDuplicate,
string? Comment = null
) : NotificationTemplate;
public record TeamsTemplate(SecretData<string> Url) : NotificationTemplate;
public record GithubAuth(string User, string PersonalAccessToken);
public record GithubIssueSearch(
string? Author,
GithubIssueState? State,
List<GithubIssueSearchMatch> FieldMatch,
[property: JsonPropertyName("string")] String str
[property: JsonPropertyName("string")] String str,
string? Author = null,
GithubIssueState? State = null
);
public record GithubIssueDuplicate(
string? Comment,
List<string> Labels,
bool Reopen
bool Reopen,
string? Comment = null
);

View File

@ -102,6 +102,9 @@ public class Program {
.AddScoped<INodeMessageOperations, NodeMessageOperations>()
.AddScoped<IRequestHandling, RequestHandling>()
.AddScoped<IImageOperations, ImageOperations>()
.AddScoped<ITeams, Teams>()
.AddScoped<IGithubIssues, GithubIssues>()
.AddScoped<IAdo, Ado>()
.AddScoped<IOnefuzzContext, OnefuzzContext>()
.AddScoped<IEndpointAuthorization, EndpointAuthorization>()
.AddScoped<INodeMessageOperations, NodeMessageOperations>()

View File

@ -29,6 +29,8 @@ public interface IContainers {
public Async.Task<Uri> AddContainerSasUrl(Uri uri, TimeSpan? duration = null);
public Async.Task<Dictionary<Container, IDictionary<string, string>>> 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}";
}
}

View File

@ -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

View File

@ -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<Notification>, 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<Notification>, 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<Notification>, INotificationOperations
}
public IAsyncEnumerable<Notification> GetNotifications(Container container) {
return QueryAsync(filter: TableClient.CreateQueryFilter($"container eq {container.String}"));
return SearchByRowKeys(new[] { container.String });
}
public IAsyncEnumerable<(Task, IEnumerable<Container>)> GetQueueTasks() {
@ -140,16 +139,4 @@ public class NotificationOperations : Orm<Notification>, 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();
}
}

View File

@ -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<ISubnet>();
public IImageOperations ImageOperations => _serviceProvider.GetRequiredService<IImageOperations>();
public EntityConverter EntityConverter => _serviceProvider.GetRequiredService<EntityConverter>();
public ITeams Teams => _serviceProvider.GetRequiredService<ITeams>();
public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService<IGithubIssues>();
public IAdo Ado => _serviceProvider.GetRequiredService<IAdo>();
}

View File

@ -59,18 +59,6 @@ public class Reports : IReports {
}
return regressionReport;
}
private IReport? ParseReportOrRegression(IEnumerable<byte> 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 { };

View File

@ -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) {

View File

@ -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<string>()
{
//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<AdoConnector> 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<string> Render(string template) {
return await _renderer.Render(template, _instanceUrl);
}
public async IAsyncEnumerable<WorkItem> ExistingWorkItems() {
var filters = new Dictionary<string, string>();
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<string, string>();
/*
# 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<string>();
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<List<string>> GetValidFields(string? project) {
return (await _client.GetFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields))
.Select(field => field.ReferenceName.ToLowerInvariant())
.ToList();
}
private async Async.Task<WorkItem> 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}");
}
}
}
}

View File

@ -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<GithubConnnector> GithubConnnectorCreator(GithubIssuesTemplate config, Container container, string filename, Renderer renderer, Uri instanceUrl, IOnefuzzContext context, ILogTracer logTracer) {
var auth = config.Auth.Secret switch {
SecretAddress<GithubAuth> sa => await context.SecretsOperations.GetSecretObj<GithubAuth>(sa.Url),
SecretValue<GithubAuth> 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<string> Render(string field) {
return await _renderer.Render(field, _instanceUrl);
}
private async Async.Task<List<Issue>> Existing() {
var query = new List<string>() {
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<Issue>();
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
);
}
}
}

View File

@ -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<Renderer> 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<string> 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;
}
}
}

View File

@ -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<Dictionary<string, string>> facts, string? text) {
title = MarkdownEscape(title);
var sections = new List<Dictionary<string, object>>() {
new() {
{"activityTitle", title},
{"facts", facts}
}
};
if (text != null) {
sections.Add(new() {
{ "text", text }
});
}
var message = new Dictionary<string, object>() {
{"@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<Dictionary<string, string>>();
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<string> {
$"[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<Dictionary<string, string>> {
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<string, string>() {
{"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;
}
}

View File

@ -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"
}
}
}
}

View File

@ -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();
}

View File

@ -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"
}
}
}

View File

@ -368,11 +368,87 @@ namespace Tests {
where Container.TryParse(nameString, out var _)
select Container.Parse(nameString);
public static Gen<ADODuplicateTemplate> AdoDuplicateTemplate() {
return Arb.Generate<Tuple<List<string>, Dictionary<string, string>, string?>>().Select(
arg =>
new ADODuplicateTemplate(
arg.Item1,
arg.Item2,
arg.Item2,
arg.Item3
)
);
}
public static Gen<AdoTemplate> AdoTemplate() {
return Arb.Generate<Tuple<Uri, SecretData<string>, NonEmptyString, List<string>, Dictionary<string, string>, 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> TeamsTemplate() {
return Arb.Generate<Tuple<SecretData<string>>>().Select(
arg =>
new TeamsTemplate(
arg.Item1
)
);
}
public static Gen<GithubAuth> GithubAuth() {
return Arb.Generate<Tuple<string>>().Select(
arg =>
new GithubAuth(
arg.Item1,
arg.Item1
)
);
}
public static Gen<GithubIssueSearch> GithubIssueSearch() {
return Arb.Generate<Tuple<List<GithubIssueSearchMatch>, string, string?, GithubIssueState?>>().Select(
arg =>
new GithubIssueSearch(
arg.Item1,
arg.Item2,
arg.Item3,
arg.Item4
)
);
}
public static Gen<GithubIssuesTemplate> GithubIssuesTemplate() {
return Arb.Generate<Tuple<SecretData<GithubAuth>, NonEmptyString, GithubIssueSearch, List<string>, 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> NotificationTemplate() {
return Gen.OneOf(new[] {
Arb.Generate<AdoTemplate>().Select(e => e as NotificationTemplate),
Arb.Generate<TeamsTemplate>().Select(e => e as NotificationTemplate),
Arb.Generate<GithubIssuesTemplate>().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 });
}
*/
}
}

View File

@ -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<NotificationTemplate>(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",

View File

@ -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"
}
}
}