Skip to content
This repository was archived by the owner on Nov 1, 2023. It is now read-only.

Commit 2a88838

Browse files
authored
Kanan/semantic validation (#3386)
* Throw exceptoins for missing area/iteration paths * Comment out feature flag check for work item creation * Revert "Comment out feature flag check for work item creation" This reverts commit ad96169. * Add some extra info to the exceptions to distinguish them * Add logging when a validation error is encountered * Add call to validate in NotificationsTest for testing * Fix area and iteration path validation * Update error messages * Fix ArgumentNullException * Add extra information to path check failure * Request classification nodes with depth equal to number of parts in the path * Pass tree structure as parameter instead of always using Areas * Remove '[PAT]' from error messages * Require project name in Area/IterationPath during validation * PR comments * Sneak in a one-line fix for empty comments * Change context messsage in response from notiifcation test
1 parent 10bb79a commit 2a88838

File tree

5 files changed

+63
-3
lines changed

5 files changed

+63
-3
lines changed

src/ApiService/ApiService/Functions/NotificationsTest.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public async Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.An
2424
}
2525

2626
var notificationTest = request.OkV;
27+
var validConfig = await notificationTest.Notification.Config.Validate();
28+
if (!validConfig.IsOk) {
29+
return await _context.RequestHandling.NotOk(req, validConfig.ErrorV, context: "notification test");
30+
}
31+
2732
var result = await _context.NotificationOperations.TriggerNotification(notificationTest.Notification.Container, notificationTest.Notification,
2833
notificationTest.Report, isLastRetryAttempt: true);
2934
var response = req.CreateResponse(HttpStatusCode.OK);

src/ApiService/ApiService/OneFuzzTypes/Enums.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public enum ErrorCode {
4848
ADO_VALIDATION_UNEXPECTED_ERROR = 491,
4949
ADO_VALIDATION_MISSING_PAT_SCOPES = 492,
5050
ADO_WORKITEM_PROCESSING_DISABLED = 494,
51+
ADO_VALIDATION_INVALID_PATH = 495,
5152
// NB: if you update this enum, also update enums.py
5253
}
5354

src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public async Async.Task<OneFuzzResult<Notification>> Create(Container container,
134134
if (await _context.FeatureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.SemanticNotificationConfigValidation)) {
135135
var validConfig = await config.Validate();
136136
if (!validConfig.IsOk) {
137+
_logTracer.LogError($"Error(s) ocurred during template validation: {{title}}{Environment.NewLine}{{errors}}", validConfig.ErrorV.Title, string.Join(Environment.NewLine, validConfig.ErrorV.Errors ?? new()));
137138
return OneFuzzResult<Notification>.Error(validConfig.ErrorV);
138139
}
139140
}

src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,38 @@ private static bool IsTransient(Exception e) {
8989
return errorCodes.Any(errorStr.Contains);
9090
}
9191

92+
private static async Async.Task<OneFuzzResultVoid> ValidatePath(string project, string path, TreeStructureGroup structureGroup, WorkItemTrackingHttpClient client) {
93+
var pathType = (structureGroup == TreeStructureGroup.Areas) ? "Area" : "Iteration";
94+
var pathParts = path.Split('\\');
95+
if (!string.Equals(pathParts[0], project, StringComparison.OrdinalIgnoreCase)) {
96+
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
97+
$"Path \"{path}\" is invalid. It must start with the project name, \"{project}\".",
98+
$"Example: \"{project}\\{path}\".",
99+
});
100+
}
101+
102+
var current = await client.GetClassificationNodeAsync(project, structureGroup, depth: pathParts.Length - 1);
103+
if (current == null) {
104+
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
105+
$"{pathType} Path \"{path}\" is invalid. \"{project}\" is not a valid project.",
106+
});
107+
}
108+
109+
foreach (var part in pathParts.Skip(1)) {
110+
var child = current.Children?.FirstOrDefault(x => string.Equals(x.Name, part, StringComparison.OrdinalIgnoreCase));
111+
if (child == null) {
112+
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
113+
$"{pathType} Path \"{path}\" is invalid. \"{part}\" is not a valid child of \"{current.Name}\".",
114+
$"Valid children of \"{current.Name}\" are: [{string.Join(',', current.Children?.Select(x => $"\"{x.Name}\"") ?? new List<string>())}].",
115+
});
116+
}
117+
118+
current = child;
119+
}
120+
121+
return OneFuzzResultVoid.Ok;
122+
}
123+
92124
public static async Async.Task<OneFuzzResultVoid> Validate(AdoTemplate config) {
93125
// Validate PAT is valid for the base url
94126
VssConnection connection;
@@ -124,10 +156,9 @@ await policy.ExecuteAsync(async () => {
124156
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PAT, "Auth token is missing or invalid");
125157
}
126158

159+
var witClient = await connection.GetClientAsync<WorkItemTrackingHttpClient>();
127160
try {
128161
// Validate unique_fields are part of the project's valid fields
129-
var witClient = await connection.GetClientAsync<WorkItemTrackingHttpClient>();
130-
131162
// The set of valid fields for this project according to ADO
132163
var projectValidFields = await GetValidFields(witClient, config.Project);
133164

@@ -162,6 +193,27 @@ await policy.ExecuteAsync(async () => {
162193
});
163194
}
164195

196+
try {
197+
// Validate AreaPath and IterationPath exist
198+
if (config.AdoFields.TryGetValue("System.AreaPath", out var areaPathString)) {
199+
var validateAreaPath = await ValidatePath(config.Project, areaPathString, TreeStructureGroup.Areas, witClient);
200+
if (!validateAreaPath.IsOk) {
201+
return validateAreaPath;
202+
}
203+
}
204+
if (config.AdoFields.TryGetValue("System.IterationPath", out var iterationPathString)) {
205+
var validateIterationPath = await ValidatePath(config.Project, iterationPathString, TreeStructureGroup.Iterations, witClient);
206+
if (!validateIterationPath.IsOk) {
207+
return validateIterationPath;
208+
}
209+
}
210+
} catch (Exception e) {
211+
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_UNEXPECTED_ERROR, new string[] {
212+
"Failed to query and validate against the classification nodes for this project",
213+
$"Exception: {e}",
214+
});
215+
}
216+
165217
return OneFuzzResultVoid.Ok;
166218
}
167219

@@ -362,7 +414,7 @@ public async Async.Task<bool> UpdateExisting(WorkItem item, IList<(string, strin
362414
return false;
363415
}
364416

365-
if (_config.OnDuplicate.Comment != null) {
417+
if (!string.IsNullOrEmpty(_config.OnDuplicate.Comment)) {
366418
var comment = _config.OnDuplicate.Comment;
367419
_ = await _client.AddCommentAsync(
368420
new CommentCreate() {

src/pytypes/onefuzztypes/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ class ErrorCode(Enum):
302302
ADO_VALIDATION_UNEXPECTED_HTTP_EXCEPTION = 490
303303
ADO_VALIDATION_UNEXPECTED_ERROR = 491
304304
ADO_VALIDATION_MISSING_PAT_SCOPES = 492
305+
ADO_VALIDATION_INVALID_PATH = 495
305306
# NB: if you update this enum, also update Enums.cs
306307

307308

0 commit comments

Comments
 (0)