From aec027307352b1bc84e022af92191692aac827dd Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 31 Jan 2017 19:35:08 -0800 Subject: [PATCH 001/251] Rough implementation consuming REST API Basic scenarios for querying by ID, IDs, or WIQL are implemented. Additional tests are still required to document / verify feature parity between the SOAP and REST implementations --- src/Qwiq.Core/ITfsTeamProjectCollection.cs | 2 + src/Qwiq.Core/IWorkItem.cs | 4 +- .../ItemAlreadyUpdatedOnServerException.cs | 2 + src/Qwiq.Core/LinkMapper.cs | 2 + .../Proxies/Rest/WorkItemLinkInfoProxy.cs | 27 ++ src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs | 257 ++++++++++++++++ .../Proxies/Rest/WorkItemStoreProxy.cs | 169 ++++++++++ .../Proxies/Rest/WorkItemTypeProxy.cs | 25 ++ .../Proxies/{ => Soap}/AttachmentProxy.cs | 3 +- .../{ => Soap}/CommonStructureServiceProxy.cs | 4 +- .../{ => Soap}/FieldCollectionProxy.cs | 4 +- .../Proxies/{ => Soap}/FieldConflictProxy.cs | 2 +- .../Proxies/{ => Soap}/FieldProxy.cs | 2 +- .../Proxies/{ => Soap}/HyperlinkProxy.cs | 2 +- .../{ => Soap}/IdentityDescriptorProxy.cs | 2 +- .../IdentityManagementServiceProxy.cs | 4 +- .../Proxies/{ => Soap}/LinkCollectionProxy.cs | 4 +- src/Qwiq.Core/Proxies/{ => Soap}/LinkProxy.cs | 2 +- .../Proxies/{ => Soap}/NodeInfoProxy.cs | 2 +- src/Qwiq.Core/Proxies/{ => Soap}/NodeProxy.cs | 4 +- .../Proxies/{ => Soap}/ProjectInfoProxy.cs | 2 +- .../{ => Soap}/ProjectPropertyProxy.cs | 2 +- .../Proxies/{ => Soap}/ProjectProxy.cs | 4 +- .../Proxies/{ => Soap}/QueryProxy.cs | 3 +- .../Proxies/{ => Soap}/RelatedLinkProxy.cs | 3 +- .../Proxies/{ => Soap}/RevisionProxy.cs | 4 +- .../{ => Soap}/TeamFoundationIdentityProxy.cs | 4 +- .../{ => Soap}/WorkItemLinkInfoProxy.cs | 2 +- .../{ => Soap}/WorkItemLinkTypeEndProxy.cs | 3 +- .../{ => Soap}/WorkItemLinkTypeProxy.cs | 3 +- .../Proxies/{ => Soap}/WorkItemProxy.cs | 16 +- .../Proxies/{ => Soap}/WorkItemStoreProxy.cs | 9 +- .../Proxies/{ => Soap}/WorkItemTypeProxy.cs | 3 +- .../Proxies/TfsTeamProjectCollectionProxy.cs | 7 + src/Qwiq.Core/QueryFactory.cs | 53 +++- src/Qwiq.Core/Qwiq.Core.csproj | 55 ++-- src/Qwiq.Core/WorkItemFields.cs | 12 + src/Qwiq.Core/WorkItemStoreFactory.cs | 55 +++- .../IdentityDescriptorProxyTests.cs | 1 + .../IdentityManagementServiceProxyTests.cs | 1 + test/Qwiq.Core.Tests/IntegrationTests.cs | 291 ++++++++++++++++++ test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 1 + test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 29 +- .../Qwiq.Mapper.Tests.csproj | 10 +- test/Qwiq.Mocks/MockWorkItem.cs | 8 +- 45 files changed, 1007 insertions(+), 97 deletions(-) create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs rename src/Qwiq.Core/Proxies/{ => Soap}/AttachmentProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/CommonStructureServiceProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/FieldCollectionProxy.cs (96%) rename src/Qwiq.Core/Proxies/{ => Soap}/FieldConflictProxy.cs (94%) rename src/Qwiq.Core/Proxies/{ => Soap}/FieldProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/HyperlinkProxy.cs (91%) rename src/Qwiq.Core/Proxies/{ => Soap}/IdentityDescriptorProxy.cs (93%) rename src/Qwiq.Core/Proxies/{ => Soap}/IdentityManagementServiceProxy.cs (98%) rename src/Qwiq.Core/Proxies/{ => Soap}/LinkCollectionProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/LinkProxy.cs (92%) rename src/Qwiq.Core/Proxies/{ => Soap}/NodeInfoProxy.cs (91%) rename src/Qwiq.Core/Proxies/{ => Soap}/NodeProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/ProjectInfoProxy.cs (91%) rename src/Qwiq.Core/Proxies/{ => Soap}/ProjectPropertyProxy.cs (89%) rename src/Qwiq.Core/Proxies/{ => Soap}/ProjectProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/QueryProxy.cs (95%) rename src/Qwiq.Core/Proxies/{ => Soap}/RelatedLinkProxy.cs (95%) rename src/Qwiq.Core/Proxies/{ => Soap}/RevisionProxy.cs (98%) rename src/Qwiq.Core/Proxies/{ => Soap}/TeamFoundationIdentityProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemLinkInfoProxy.cs (94%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemLinkTypeEndProxy.cs (96%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemLinkTypeProxy.cs (96%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemStoreProxy.cs (95%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemTypeProxy.cs (94%) create mode 100644 src/Qwiq.Core/WorkItemFields.cs create mode 100644 test/Qwiq.Core.Tests/IntegrationTests.cs diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index 57dba2a6..323b285b 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -11,6 +11,8 @@ public interface ITfsTeamProjectCollection internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection, IDisposable { T GetService(); + + T GetClient(); } } diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 59fcc9ea..a5a75b91 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -91,7 +91,7 @@ public interface IWorkItem /// int RelatedLinkCount { get; } - int Rev { get; } + long Rev { get; } /// /// Gets a System.DateTime object that represents the revision date and time @@ -102,7 +102,7 @@ public interface IWorkItem /// /// Gets the integer that represents the revision number of this work item. /// - int Revision { get; } + long Revision { get; } /// /// Gets an object that represents a collection of valid revision numbers for this work diff --git a/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs b/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs index 6e028baa..35d75914 100644 --- a/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs +++ b/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs @@ -3,6 +3,8 @@ using System.Linq; using Microsoft.Qwiq.Exceptions; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq diff --git a/src/Qwiq.Core/LinkMapper.cs b/src/Qwiq.Core/LinkMapper.cs index 756d1a0c..e2f01b3e 100644 --- a/src/Qwiq.Core/LinkMapper.cs +++ b/src/Qwiq.Core/LinkMapper.cs @@ -1,6 +1,8 @@ using System; using Microsoft.Qwiq.Exceptions; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs new file mode 100644 index 00000000..21f8e4a0 --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs @@ -0,0 +1,27 @@ +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + public class WorkItemLinkInfoProxy : IWorkItemLinkInfo + { + private readonly WorkItemLink _item; + + internal WorkItemLinkInfoProxy(WorkItemLink item) + { + _item = item; + } + + public bool IsLocked => throw new NotImplementedException(); + + public int LinkTypeId => throw new NotImplementedException(); + + public int SourceId => (_item.Source?.Id).GetValueOrDefault(); + + public int TargetId => (_item.Target?.Id).GetValueOrDefault(); + } +} diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs new file mode 100644 index 00000000..10b1edb3 --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.TeamFoundation.WorkItemTracking.Client; +using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; + +using WorkItem = Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + public class WorkItemProxy : IWorkItem + { + private readonly WorkItem _item; + + internal WorkItemProxy(WorkItem item) + { + _item = item; + Type = new WorkItemTypeProxy(GetValue(CoreFieldRefNames.WorkItemType)); + Uri = new Uri(item.Url); + } + + public string AreaPath + { + get + { + return GetValue(CoreFieldRefNames.AreaPath); + } + set + { + SetValue(CoreFieldRefNames.AreaPath, value); + } + } + + public string AssignedTo + { + get + { + return GetValue(CoreFieldRefNames.AssignedTo); + } + set + { + SetValue(CoreFieldRefNames.AssignedTo, value); + } + } + + public int AttachedFileCount => GetValue(CoreFieldRefNames.AttachedFileCount); + + public IEnumerable Attachments { get { throw new NotImplementedException(); } } + + public string ChangedBy + { + get { return GetValue(CoreFieldRefNames.ChangedBy); } + set + { + SetValue(CoreFieldRefNames.ChangedBy, value); + } + } + + public DateTime ChangedDate + { + get { return (DateTime)GetValue(CoreFieldRefNames.ChangedDate); } + set + { + SetValue(CoreFieldRefNames.ChangedDate, value); + } + } + + public string CreatedBy + { + get { return GetValue(CoreFieldRefNames.CreatedBy); } + set + { + SetValue(CoreFieldRefNames.CreatedBy, value); + } + } + + public DateTime CreatedDate + { + get { return (DateTime)GetValue(CoreFieldRefNames.CreatedDate); } + set + { + SetValue(CoreFieldRefNames.CreatedDate, value); + } + } + + public string Description + { + get + { + return GetValue(CoreFieldRefNames.Description); + } + set + { + SetValue(CoreFieldRefNames.Description, value); + } + } + + public int ExternalLinkCount => GetValue(CoreFieldRefNames.ExternalLinkCount); + + public IFieldCollection Fields { get { throw new NotImplementedException(); } } + + public string History + { + get { return GetValue(CoreFieldRefNames.History) as string ?? string.Empty; } + set + { + SetValue(CoreFieldRefNames.History, value); + } + } + + public int HyperLinkCount => GetValue(CoreFieldRefNames.HyperLinkCount); + + public int Id => _item.Id.GetValueOrDefault(0); + + public bool IsDirty { get { throw new NotImplementedException(); } } + + public string IterationPath + { + get + { + return GetValue(CoreFieldRefNames.IterationPath); + } + set + { + SetValue(CoreFieldRefNames.IterationPath, value); + } + } + + public string Keywords + { + get { return GetValue(WorkItemFields.Keywords); } + set { SetValue(WorkItemFields.Keywords, value); } + } + + public ICollection Links { get { throw new NotImplementedException(); } } + + public int RelatedLinkCount => GetValue(CoreFieldRefNames.RelatedLinkCount); + + public long Rev => GetValue(CoreFieldRefNames.Rev); + + public DateTime RevisedDate => GetValue(CoreFieldRefNames.RevisedDate); + + public long Revision => Rev; + + public IEnumerable Revisions { get { throw new NotImplementedException(); } } + + public string State + { + get { return GetValue(CoreFieldRefNames.State); } + set { SetValue(CoreFieldRefNames.State, value); } + } + + public string Tags + { + get { return GetValue(CoreFieldRefNames.Tags); } + set { SetValue(CoreFieldRefNames.Tags, value); } + } + + public string Title + { + get { return GetValue(CoreFieldRefNames.Title); } + set { SetValue(CoreFieldRefNames.Title, value); } + } + + public IWorkItemType Type { get; } + + public Uri Uri { get; } + + public object this[string name] + { + get + { + return GetValue(name); + } + set + { + SetValue(name, value); + } + } + + public void Close() + { + } + + public IWorkItem Copy() + { + throw new NotImplementedException(); + } + + public IHyperlink CreateHyperlink(string location) + { + throw new NotImplementedException(); + } + + public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) + { + throw new NotImplementedException(); + } + + public bool IsValid() + { + throw new NotImplementedException(); + } + + public void Open() + { + } + + public void PartialOpen() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + + public void Save() + { + throw new NotImplementedException(); + } + + public void Save(SaveFlags saveFlags) + { + throw new NotImplementedException(); + } + + public IEnumerable Validate() + { + throw new NotImplementedException(); + } + + private T GetValue(string field) + { + return (T)GetValue(field); + } + + private object GetValue(string field) + { + if (!_item.Fields.TryGetValue(field, out object val)) + { + // To preserve OM compatability + throw new FieldDefinitionNotExistException( + $"TF26026: A field definition ID {field} in the work item type definition file does not exist. Add a definition for this field ID, or remove the reference to the field ID and try again."); + } + return val; + } + + private void SetValue(string field, object value) + { + _item.Fields[field] = value; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs new file mode 100644 index 00000000..7a5bb3ae --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Qwiq.Exceptions; +using Microsoft.Qwiq.Proxies.Soap; +using Microsoft.TeamFoundation.WorkItemTracking.Client.Wiql; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + + + public class WorkItemStoreProxy : IWorkItemStore + { + private IInternalTfsTeamProjectCollection _teamProjectCollection; + + private readonly WorkItemTrackingHttpClient _workItemStore; + + + + internal WorkItemStoreProxy( + IInternalTfsTeamProjectCollection teamProjectCollection, + WorkItemTrackingHttpClient workItemStore) + { + if (teamProjectCollection == null) throw new ArgumentNullException(nameof(teamProjectCollection)); + if (workItemStore == null) throw new ArgumentNullException(nameof(workItemStore)); + + _teamProjectCollection = teamProjectCollection; + _workItemStore = workItemStore; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + _teamProjectCollection?.Dispose(); + _teamProjectCollection = null; + } + } + + public IEnumerable Query(string wiql, bool dayPrecision = false) + { + if (dayPrecision) throw new NotSupportedException(); + + var p = Parser.ParseSyntax(wiql); + var w = new Wiql() { Query = p.ToString() }; + + var fields = new List(); + for (var i = 0; i < p.Fields.Count; i++) + { + var field = p.Fields[i]; + fields.Add(field.Value); + } + + + var result = _workItemStore.QueryByWiqlAsync(w, "OS").GetAwaiter().GetResult(); + if (result.WorkItems.Any()) + { + int skip = 0; + const int BatchSize = 100; + IEnumerable workItemRefs; + do + { + workItemRefs = result.WorkItems.Skip(skip).Take(BatchSize); + if (workItemRefs.Any()) + { + // TODO: Support AsOf + var workItems = _workItemStore.GetWorkItemsAsync(workItemRefs.Select(wir => wir.Id), fields,null, null).GetAwaiter().GetResult(); + foreach (var workItem in workItems) + { + yield return new WorkItemProxy(workItem); + } + } + skip += BatchSize; + } + while (workItemRefs.Count() == BatchSize); + } + } + private static IEnumerable Ids(WorkItemQueryResult result, int skip = 0) + { + return result.WorkItemRelations.Where(r => r.Target != null).Select(r => r.Target.Id) + .Union(result.WorkItemRelations.Where(r => r.Source != null).Select(r => r.Source.Id)) + .Skip(skip) + .Take(100); + } + + public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) + { + if (dayPrecision) throw new NotSupportedException(); + + var w = new Wiql() { Query = wiql }; + + var result = _workItemStore.QueryByWiqlAsync(w, "OS").GetAwaiter().GetResult(); + if (result.WorkItemRelations.Any()) + { + int skip = 0; + const int BatchSize = 200; + IEnumerable workItemRefs; + do + { + workItemRefs = result.WorkItemRelations.Skip(skip).Take(BatchSize); + if (workItemRefs.Any()) + { + + foreach (var workItem in workItemRefs) + { + // write work item to console + yield return new WorkItemLinkInfoProxy(workItem); + } + } + skip += BatchSize; + } + while (workItemRefs.Count() == BatchSize); + } + } + + public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + { + if (!ids.Any()) + { + yield return null; + } + + var wis = _workItemStore.GetWorkItemsAsync(ids, null, asOf, WorkItemExpand.Fields).GetAwaiter().GetResult(); + foreach (var workItem in wis) + { + // write work item to console + yield return new WorkItemProxy(workItem); + } + } + + public IWorkItem Query(int id, DateTime? asOf = null) + { + var wi = _workItemStore.GetWorkItemAsync(id, null, asOf, WorkItemExpand.Fields).GetAwaiter().GetResult(); + return new WorkItemProxy(wi); + } + + public ITfsTeamProjectCollection TeamProjectCollection => _teamProjectCollection; + + public IEnumerable Projects + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable WorkItemLinkTypes { get; } + + public TimeZone TimeZone + { + get + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs new file mode 100644 index 00000000..888ca5d0 --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + public class WorkItemTypeProxy : IWorkItemType + { + internal WorkItemTypeProxy(string name) + { + Name = name; + } + + public string Description { get; } + + public string Name { get; } + + public IWorkItem NewWorkItem() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Qwiq.Core/Proxies/AttachmentProxy.cs b/src/Qwiq.Core/Proxies/Soap/AttachmentProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/AttachmentProxy.cs rename to src/Qwiq.Core/Proxies/Soap/AttachmentProxy.cs index 84d7381a..bdc885d6 100644 --- a/src/Qwiq.Core/Proxies/AttachmentProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/AttachmentProxy.cs @@ -1,7 +1,8 @@ using System; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class AttachmentProxy : IAttachment { diff --git a/src/Qwiq.Core/Proxies/CommonStructureServiceProxy.cs b/src/Qwiq.Core/Proxies/Soap/CommonStructureServiceProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/CommonStructureServiceProxy.cs rename to src/Qwiq.Core/Proxies/Soap/CommonStructureServiceProxy.cs index 7b3bf46f..243058a4 100644 --- a/src/Qwiq.Core/Proxies/CommonStructureServiceProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/CommonStructureServiceProxy.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Xml; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class CommonStructureServiceProxy : ICommonStructureService { diff --git a/src/Qwiq.Core/Proxies/FieldCollectionProxy.cs b/src/Qwiq.Core/Proxies/Soap/FieldCollectionProxy.cs similarity index 96% rename from src/Qwiq.Core/Proxies/FieldCollectionProxy.cs rename to src/Qwiq.Core/Proxies/Soap/FieldCollectionProxy.cs index 9ed9952a..3b1776db 100644 --- a/src/Qwiq.Core/Proxies/FieldCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/FieldCollectionProxy.cs @@ -1,10 +1,12 @@ using System.Collections; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { internal class FieldCollectionProxy : IFieldCollection { diff --git a/src/Qwiq.Core/Proxies/FieldConflictProxy.cs b/src/Qwiq.Core/Proxies/Soap/FieldConflictProxy.cs similarity index 94% rename from src/Qwiq.Core/Proxies/FieldConflictProxy.cs rename to src/Qwiq.Core/Proxies/Soap/FieldConflictProxy.cs index 48f28a68..236eb82e 100644 --- a/src/Qwiq.Core/Proxies/FieldConflictProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/FieldConflictProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class FieldConflictProxy : IFieldConflict { diff --git a/src/Qwiq.Core/Proxies/FieldProxy.cs b/src/Qwiq.Core/Proxies/Soap/FieldProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/FieldProxy.cs rename to src/Qwiq.Core/Proxies/Soap/FieldProxy.cs index 79f92f5e..cc79b6d1 100644 --- a/src/Qwiq.Core/Proxies/FieldProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/FieldProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class FieldProxy : IField { diff --git a/src/Qwiq.Core/Proxies/HyperlinkProxy.cs b/src/Qwiq.Core/Proxies/Soap/HyperlinkProxy.cs similarity index 91% rename from src/Qwiq.Core/Proxies/HyperlinkProxy.cs rename to src/Qwiq.Core/Proxies/Soap/HyperlinkProxy.cs index 3ae39357..b05f0dcb 100644 --- a/src/Qwiq.Core/Proxies/HyperlinkProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/HyperlinkProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class HyperlinkProxy : LinkProxy, IHyperlink { diff --git a/src/Qwiq.Core/Proxies/IdentityDescriptorProxy.cs b/src/Qwiq.Core/Proxies/Soap/IdentityDescriptorProxy.cs similarity index 93% rename from src/Qwiq.Core/Proxies/IdentityDescriptorProxy.cs rename to src/Qwiq.Core/Proxies/Soap/IdentityDescriptorProxy.cs index 26dbcf5d..489100de 100644 --- a/src/Qwiq.Core/Proxies/IdentityDescriptorProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/IdentityDescriptorProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Framework.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class IdentityDescriptorProxy : IIdentityDescriptor { diff --git a/src/Qwiq.Core/Proxies/IdentityManagementServiceProxy.cs b/src/Qwiq.Core/Proxies/Soap/IdentityManagementServiceProxy.cs similarity index 98% rename from src/Qwiq.Core/Proxies/IdentityManagementServiceProxy.cs rename to src/Qwiq.Core/Proxies/Soap/IdentityManagementServiceProxy.cs index 2c814653..79068806 100644 --- a/src/Qwiq.Core/Proxies/IdentityManagementServiceProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/IdentityManagementServiceProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.Framework; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class IdentityManagementServiceProxy : IIdentityManagementService { diff --git a/src/Qwiq.Core/Proxies/LinkCollectionProxy.cs b/src/Qwiq.Core/Proxies/Soap/LinkCollectionProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/LinkCollectionProxy.cs rename to src/Qwiq.Core/Proxies/Soap/LinkCollectionProxy.cs index aad0e81f..c11c0b4b 100644 --- a/src/Qwiq.Core/Proxies/LinkCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/LinkCollectionProxy.cs @@ -1,10 +1,10 @@ -using System; using System.Collections; using System.Collections.Generic; using System.Linq; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class LinkCollectionProxy : ICollection { diff --git a/src/Qwiq.Core/Proxies/LinkProxy.cs b/src/Qwiq.Core/Proxies/Soap/LinkProxy.cs similarity index 92% rename from src/Qwiq.Core/Proxies/LinkProxy.cs rename to src/Qwiq.Core/Proxies/Soap/LinkProxy.cs index 4c7ee8c3..d5dcc308 100644 --- a/src/Qwiq.Core/Proxies/LinkProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/LinkProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class LinkProxy : ILink { diff --git a/src/Qwiq.Core/Proxies/NodeInfoProxy.cs b/src/Qwiq.Core/Proxies/Soap/NodeInfoProxy.cs similarity index 91% rename from src/Qwiq.Core/Proxies/NodeInfoProxy.cs rename to src/Qwiq.Core/Proxies/Soap/NodeInfoProxy.cs index 15085a7f..eea08e63 100644 --- a/src/Qwiq.Core/Proxies/NodeInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/NodeInfoProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class NodeInfoProxy : INodeInfo { diff --git a/src/Qwiq.Core/Proxies/NodeProxy.cs b/src/Qwiq.Core/Proxies/Soap/NodeProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/NodeProxy.cs rename to src/Qwiq.Core/Proxies/Soap/NodeProxy.cs index 3b534837..792c5654 100644 --- a/src/Qwiq.Core/Proxies/NodeProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/NodeProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class NodeProxy : INode { diff --git a/src/Qwiq.Core/Proxies/ProjectInfoProxy.cs b/src/Qwiq.Core/Proxies/Soap/ProjectInfoProxy.cs similarity index 91% rename from src/Qwiq.Core/Proxies/ProjectInfoProxy.cs rename to src/Qwiq.Core/Proxies/Soap/ProjectInfoProxy.cs index 7e5e6a12..1a41070a 100644 --- a/src/Qwiq.Core/Proxies/ProjectInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/ProjectInfoProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class ProjectInfoProxy : IProjectInfo { diff --git a/src/Qwiq.Core/Proxies/ProjectPropertyProxy.cs b/src/Qwiq.Core/Proxies/Soap/ProjectPropertyProxy.cs similarity index 89% rename from src/Qwiq.Core/Proxies/ProjectPropertyProxy.cs rename to src/Qwiq.Core/Proxies/Soap/ProjectPropertyProxy.cs index d6b308ee..3907d09b 100644 --- a/src/Qwiq.Core/Proxies/ProjectPropertyProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/ProjectPropertyProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class ProjectPropertyProxy : IProjectProperty { diff --git a/src/Qwiq.Core/Proxies/ProjectProxy.cs b/src/Qwiq.Core/Proxies/Soap/ProjectProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/ProjectProxy.cs rename to src/Qwiq.Core/Proxies/Soap/ProjectProxy.cs index e8ed2031..74791b6e 100644 --- a/src/Qwiq.Core/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/ProjectProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class ProjectProxy : IProject { diff --git a/src/Qwiq.Core/Proxies/QueryProxy.cs b/src/Qwiq.Core/Proxies/Soap/QueryProxy.cs similarity index 95% rename from src/Qwiq.Core/Proxies/QueryProxy.cs rename to src/Qwiq.Core/Proxies/Soap/QueryProxy.cs index 627b8ed5..5607eea1 100644 --- a/src/Qwiq.Core/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/QueryProxy.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class QueryProxy : IQuery { diff --git a/src/Qwiq.Core/Proxies/RelatedLinkProxy.cs b/src/Qwiq.Core/Proxies/Soap/RelatedLinkProxy.cs similarity index 95% rename from src/Qwiq.Core/Proxies/RelatedLinkProxy.cs rename to src/Qwiq.Core/Proxies/Soap/RelatedLinkProxy.cs index 8fc3126b..fe1b8ff1 100644 --- a/src/Qwiq.Core/Proxies/RelatedLinkProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/RelatedLinkProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class RelatedLinkProxy : LinkProxy, IRelatedLink { diff --git a/src/Qwiq.Core/Proxies/RevisionProxy.cs b/src/Qwiq.Core/Proxies/Soap/RevisionProxy.cs similarity index 98% rename from src/Qwiq.Core/Proxies/RevisionProxy.cs rename to src/Qwiq.Core/Proxies/Soap/RevisionProxy.cs index 909ef1b5..6f23f4d9 100644 --- a/src/Qwiq.Core/Proxies/RevisionProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/RevisionProxy.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { /// /// Wrapper around the TFS RevisionProxy. This exists so that every agent doesn't need to reference diff --git a/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs b/src/Qwiq.Core/Proxies/Soap/TeamFoundationIdentityProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs rename to src/Qwiq.Core/Proxies/Soap/TeamFoundationIdentityProxy.cs index 2caf910a..938d5d16 100644 --- a/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/TeamFoundationIdentityProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.Framework.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class TeamFoundationIdentityProxy : ITeamFoundationIdentity { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkInfoProxy.cs similarity index 94% rename from src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemLinkInfoProxy.cs index aa51dbe2..7530235a 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkInfoProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemLinkInfoProxy : IWorkItemLinkInfo { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeEndProxy.cs similarity index 96% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeEndProxy.cs index 1585fd51..6c3d44d8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeEndProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeProxy.cs similarity index 96% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeProxy.cs index ff55ecfa..7d975459 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemLinkTypeProxy : IWorkItemLinkType { diff --git a/src/Qwiq.Core/Proxies/WorkItemProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/WorkItemProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemProxy.cs index 4941bde0..e1b17234 100644 --- a/src/Qwiq.Core/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { /// /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference @@ -21,8 +23,8 @@ internal WorkItemProxy(Tfs.WorkItem item) public string AssignedTo { - get { return _item["Assigned To"].ToString(); } - set { _item["Assigned To"] = value; } + get { return _item[Tfs.CoreFieldReferenceNames.AssignedTo].ToString(); } + set { _item[Tfs.CoreFieldReferenceNames.AssignedTo] = value; } } /// @@ -180,7 +182,7 @@ public DateTime RevisedDate /// /// Gets the integer that represents the revision number of this work item. /// - public int Revision + public long Revision { get { return _item.Revision; } } @@ -211,8 +213,8 @@ public string Tags public string Keywords { - get { return (string)_item["Keywords"]; } - set { _item["Keywords"] = value; } + get { return (string)_item[WorkItemFields.Keywords]; } + set { _item[WorkItemFields.Keywords] = value; } } /// @@ -244,7 +246,7 @@ public Uri Uri get { return _item.Uri; } } - public int Rev + public long Rev { get { return _item.Rev; } } diff --git a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemStoreProxy.cs similarity index 95% rename from src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemStoreProxy.cs index 90f77969..1bcf8d6c 100644 --- a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemStoreProxy.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { /// /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference @@ -14,7 +16,7 @@ namespace Microsoft.Qwiq.Proxies public class WorkItemStoreProxy : IWorkItemStore { private readonly IQueryFactory _queryFactory; - private readonly IInternalTfsTeamProjectCollection _tfs; + private IInternalTfsTeamProjectCollection _tfs; private readonly TfsWorkItem.WorkItemStore _workItemStore; internal WorkItemStoreProxy(IInternalTfsTeamProjectCollection tfs, TfsWorkItem.WorkItemStore workItemStore, IQueryFactory queryFactory) @@ -36,7 +38,8 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _tfs.Dispose(); + _tfs?.Dispose(); + _tfs = null; } } #endregion diff --git a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemTypeProxy.cs similarity index 94% rename from src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemTypeProxy.cs index ec64e813..f30de7f8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemTypeProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemTypeProxy : IWorkItemType { diff --git a/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs b/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs index 0ad893c6..2b7400a9 100644 --- a/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs @@ -1,5 +1,7 @@ using System; using Microsoft.Qwiq.Exceptions; +using Microsoft.Qwiq.Proxies.Soap; + using Tfs = Microsoft.TeamFoundation; namespace Microsoft.Qwiq.Proxies @@ -31,6 +33,11 @@ public T GetService() return _tfs.GetService(); } + public T GetClient() + { + return _tfs.GetClient(); + } + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/src/Qwiq.Core/QueryFactory.cs b/src/Qwiq.Core/QueryFactory.cs index 9d43db9d..e07719bd 100644 --- a/src/Qwiq.Core/QueryFactory.cs +++ b/src/Qwiq.Core/QueryFactory.cs @@ -1,5 +1,8 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq @@ -9,23 +12,51 @@ internal interface IQueryFactory IQuery Create(string wiql, bool dayPrecision); } - internal class QueryFactory : IQueryFactory + namespace Microsoft.Qwiq.Soap { - private readonly Tfs.WorkItemStore _store; - - private QueryFactory(Tfs.WorkItemStore store) + internal class QueryFactory : IQueryFactory { - _store = store; - } + private readonly Tfs.WorkItemStore _store; - public static QueryFactory GetInstance(Tfs.WorkItemStore store) - { - return new QueryFactory(store); + private QueryFactory(Tfs.WorkItemStore store) + { + _store = store; + } + + public static IQueryFactory GetInstance(Tfs.WorkItemStore store) + { + return new QueryFactory(store); + } + + public IQuery Create(string wiql, bool dayPrecision) + { + return + ExceptionHandlingDynamicProxyFactory.Create( + new QueryProxy(new Tfs.Query(_store, wiql, null, dayPrecision))); + } } + } - public IQuery Create(string wiql, bool dayPrecision) + namespace Microsoft.Qwiq.Rest + { + internal class QueryFactory : IQueryFactory { - return ExceptionHandlingDynamicProxyFactory.Create(new QueryProxy(new Tfs.Query(_store, wiql, null, dayPrecision))); + private readonly WorkItemTrackingHttpClient _store; + + private QueryFactory(WorkItemTrackingHttpClient store) + { + _store = store; + } + + public static IQueryFactory GetInstance(WorkItemTrackingHttpClient store) + { + return new QueryFactory(store); + } + + public IQuery Create(string wiql, bool dayPrecision) + { + throw new System.NotImplementedException(); + } } } } diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 584bba9f..75e59ee1 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -287,38 +287,43 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + diff --git a/src/Qwiq.Core/WorkItemFields.cs b/src/Qwiq.Core/WorkItemFields.cs new file mode 100644 index 00000000..8444baa8 --- /dev/null +++ b/src/Qwiq.Core/WorkItemFields.cs @@ -0,0 +1,12 @@ +using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; + +namespace Microsoft.Qwiq +{ + public static class WorkItemFields + { + public const string Keywords = "Microsoft.VSTS.Common.Keywords"; + } + + + +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemStoreFactory.cs b/src/Qwiq.Core/WorkItemStoreFactory.cs index 8ed74a22..89002307 100644 --- a/src/Qwiq.Core/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core/WorkItemStoreFactory.cs @@ -3,21 +3,32 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; + using Microsoft.Qwiq.Proxies; +using TfsSoap = Microsoft.Qwiq.Proxies.Soap; +using TfsRest = Microsoft.Qwiq.Proxies.Rest; using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Build.WebApi; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.WorkItemTracking.Client; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; namespace Microsoft.Qwiq { public interface IWorkItemStoreFactory { - IWorkItemStore Create(Uri endpoint, TfsCredentials credentials); + IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default); + + IWorkItemStore Create(Uri endpoint, IEnumerable credentials, ClientType type = ClientType.Default); + } - IWorkItemStore Create(Uri endpoint, IEnumerable credentials); + public enum ClientType + { + Default, + Soap, + Rest } - + public class WorkItemStoreFactory : IWorkItemStoreFactory { private static readonly Lazy Instance = new Lazy(() => new WorkItemStoreFactory()); @@ -31,14 +42,14 @@ public static IWorkItemStoreFactory GetInstance() return Instance.Value; } - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) + public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default) { - return Create(endpoint, new [] { credentials }); + return Create(endpoint, new[] { credentials }, type); } - public IWorkItemStore Create(Uri endpoint, IEnumerable credentials) + public IWorkItemStore Create(Uri endpoint, IEnumerable credentials, ClientType type = ClientType.Default) { - Func queryFactoryFunc = QueryFactory.GetInstance; + foreach (var credential in credentials) { try @@ -48,10 +59,20 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia System.Diagnostics.Trace.TraceInformation("TFS connection attempt success with {0}/{1}.", credential.Credentials.Windows.GetType(), credential.Credentials.Federated.GetType()); var tfs = ExceptionHandlingDynamicProxyFactory.Create(new TfsTeamProjectCollectionProxy(tfsNative)); - var workItemStore = tfs.GetService(); - var queryFactory = queryFactoryFunc.Invoke(workItemStore); - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemStoreProxy(tfs, workItemStore, queryFactory)); + IWorkItemStore wis = null; + switch (type) + { + case ClientType.Rest: + wis = CreateRestWorkItemStore(tfs); + break; + case ClientType.Soap: + case ClientType.Default: + wis = CreateSoapWorkItemStore(tfs); + break; + } + + return ExceptionHandlingDynamicProxyFactory.Create(wis); } catch (TeamFoundationServerUnauthorizedException e) { @@ -63,6 +84,20 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia throw new AccessDeniedException("Invalid credentials"); } + private static IWorkItemStore CreateRestWorkItemStore(IInternalTfsTeamProjectCollection tfs) + { + var workItemStore = tfs.GetClient(); + return new TfsRest.WorkItemStoreProxy(tfs, workItemStore); + } + + private static IWorkItemStore CreateSoapWorkItemStore(IInternalTfsTeamProjectCollection tfs) + { + + var workItemStore = tfs.GetService(); + var queryFactory = Microsoft.Qwiq.Soap.QueryFactory.GetInstance(workItemStore); + return new TfsSoap.WorkItemStoreProxy(tfs, workItemStore, queryFactory); + } + private static TfsTeamProjectCollection ConnectToTfsCollection( Uri endpoint, TfsClientCredentials credentials) diff --git a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs index 70d7caa7..7cc6936d 100644 --- a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs @@ -1,4 +1,5 @@ using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; diff --git a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs index 511e1fa2..b83f4f7c 100644 --- a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs @@ -2,6 +2,7 @@ using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; diff --git a/test/Qwiq.Core.Tests/IntegrationTests.cs b/test/Qwiq.Core.Tests/IntegrationTests.cs new file mode 100644 index 00000000..0b178f41 --- /dev/null +++ b/test/Qwiq.Core.Tests/IntegrationTests.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Core.Tests +{ + [TestClass] + public class SingleIdTests : IntegrationContextSpecification + { + private const int Id = 10726528; + + public override void When() + { + SoapResult.WorkItem = SoapResult.WorkItemStore.Query(Id); + RestResult.WorkItem = RestResult.WorkItemStore.Query(Id); + } + } + + [TestClass] + public class MultipleIdTests : IntegrationContextSpecification + { + private const int Id = 10726528; + + public override void When() + { + SoapResult.WorkItem = SoapResult.WorkItemStore.Query(new[] { Id }).Single(); + RestResult.WorkItem = RestResult.WorkItemStore.Query(new[] { Id }).Single(); + } + } + + [TestClass] + public class WiqlFlatQueryTests : IntegrationContextSpecification + { + private const int Id = 10726528; + + private static readonly string Wiql = $"SELECT {string.Join(", ", CoreFields)} FROM WorkItems WHERE [System.Id] = {Id}"; + + public override void When() + { + RestResult.WorkItem = RestResult.WorkItemStore.Query(Wiql).Single(); + SoapResult.WorkItem = SoapResult.WorkItemStore.Query(Wiql).Single(); + } + } + + [TestClass] + public class WiqlHierarchyQueryTests : ContextSpecification + { + protected Result RestResult { get; set; } + + protected Result SoapResult { get; set; } + + public override void Given() + { + var credentials = Credentials.CredentialsFactory.CreateCredentials(); + var fac = WorkItemStoreFactory.GetInstance(); + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; + RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + } + + public override void When() + { + const string WIQL = @" +SELECT * +FROM WorkItemLinks +WHERE + [Source].[System.TeamProject] = 'OS' AND + [Source].[System.ID] = 10726528 AND + [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' AND + [Target].[System.WorkItemType] = 'Scenario' +mode(recursive) +"; + + RestResult.WorkItemLinks = RestResult.WorkItemStore.QueryLinks(WIQL).ToList(); + SoapResult.WorkItemLinks = SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(); + } + + public override void Cleanup() + { + SoapResult?.Dispose(); + RestResult?.Dispose(); + } + + [TestMethod] + public void SOAP_Links_returned() + { + SoapResult.WorkItemLinks.ShouldNotBeNull(); + } + + [TestMethod] + public void REST_Links_returned() + { + RestResult.WorkItemLinks.ShouldNotBeNull(); + } + + [TestMethod] + public void Same_number_of_links_returned() + { + RestResult.WorkItemLinks.Count().ShouldEqual(SoapResult.WorkItemLinks.Count()); + } + + [TestMethod] + public void WorkItemLink_SourceId_TargetId_are_equal() + { + for (var i = 0; i < RestResult.WorkItemLinks.Count(); i++) + { + var r = RestResult.WorkItemLinks.ElementAt(i); + var s = SoapResult.WorkItemLinks.ElementAt(i); + + r.SourceId.ShouldEqual(s.SourceId); + r.TargetId.ShouldEqual(s.TargetId); + } + } + + protected class Result : IDisposable + { + public IWorkItem WorkItem { get; set; } + + public IEnumerable WorkItemLinks { get; set; } + + public IWorkItemStore WorkItemStore { get; set; } + + public void Dispose() + { + WorkItemStore?.Dispose(); + } + } + } + + + public abstract class IntegrationContextSpecification : ContextSpecification + { + protected Result RestResult { get; set; } + + protected Result SoapResult { get; set; } + + [TestMethod] + public void AreaPath_is_equal() + { + RestResult.WorkItem.AreaPath.ShouldEqual(SoapResult.WorkItem.AreaPath); + } + + [TestMethod] + public void AssignedTo_is_equal() + { + RestResult.WorkItem.AssignedTo.ShouldEqual(SoapResult.WorkItem.AssignedTo); + } + + [TestMethod] + public void ChangedBy_is_equal() + { + RestResult.WorkItem.ChangedBy.ShouldEqual(SoapResult.WorkItem.ChangedBy); + } + + [TestMethod] + public void ChangedDate_is_equal() + { + RestResult.WorkItem.ChangedDate.ShouldEqual(SoapResult.WorkItem.ChangedDate.ToLocalTime()); + } + + public override void Cleanup() + { + SoapResult?.Dispose(); + RestResult?.Dispose(); + } + + protected static readonly string[] CoreFields = + { + "System.AreaPath", "System.AssignedTo", "System.AttachedFileCount", "System.ChangedBy", + "System.ChangedDate", "System.CreatedBy", "System.CreatedDate", "System.Description", + "System.ExternalLinkCount", "System.History", "System.HyperLinkCount", "System.Id", + "System.IterationPath", "System.RelatedLinkCount", "System.Rev", "System.RevisedDate", + "System.State", "System.Title", "System.WorkItemType", + }; + + [TestMethod] + public void CoreFields_are_equal() + { + + + foreach (var field in CoreFields) + { + RestResult.WorkItem[field].ShouldEqual(SoapResult.WorkItem[field]); + } + } + + [TestMethod] + public void CreatedBy_is_equal() + { + RestResult.WorkItem.CreatedBy.ShouldEqual(SoapResult.WorkItem.CreatedBy); + } + + [TestMethod] + public void Rev_is_equal() + { + RestResult.WorkItem.Rev.ShouldEqual(SoapResult.WorkItem.Rev); + } + + [TestMethod] + public void CreatedDate_is_equal() + { + RestResult.WorkItem.CreatedDate.ShouldEqual(SoapResult.WorkItem.CreatedDate); + } + + [TestMethod] + public void Description_is_equal() + { + RestResult.WorkItem.Description.ShouldEqual(SoapResult.WorkItem.Description); + } + + public override void Given() + { + var credentials = Credentials.CredentialsFactory.CreateCredentials(); + var fac = WorkItemStoreFactory.GetInstance(); + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; + RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + } + + [TestMethod] + public void History_is_equal() + { + RestResult.WorkItem.History.ShouldEqual(SoapResult.WorkItem.History); + } + + [TestMethod] + public void Id_is_equal() + { + RestResult.WorkItem.Id.ShouldEqual(SoapResult.WorkItem.Id); + } + + [TestMethod] + public void IterationPath_is_equal() + { + RestResult.WorkItem.IterationPath.ShouldEqual(SoapResult.WorkItem.IterationPath); + } + + [TestMethod] + public void REST_WorkItem_is_returned() + { + RestResult.WorkItem.ShouldNotBeNull(); + } + + [TestMethod] + public void SOAP_WorkItem_is_returned() + { + SoapResult.WorkItem.ShouldNotBeNull(); + } + + [TestMethod] + public void State_is_equal() + { + RestResult.WorkItem.State.ShouldEqual(SoapResult.WorkItem.State); + } + + [TestMethod] + public void Tags_is_equal() + { + RestResult.WorkItem.Tags.ShouldEqual(SoapResult.WorkItem.Tags); + } + + [TestMethod] + public void Title_is_equal() + { + RestResult.WorkItem.Title.ShouldEqual(SoapResult.WorkItem.Title); + } + + protected class Result : IDisposable + { + public IWorkItem WorkItem { get; set; } + + public IEnumerable Links { get; set; } + + public IWorkItemStore WorkItemStore { get; set; } + + public void Dispose() + { + WorkItemStore?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index abb8b272..0731e368 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -293,6 +293,7 @@ + diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 7d70ef9a..c5ce95d5 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -3,24 +3,41 @@ using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Proxies; +using TfsSoap = Microsoft.Qwiq.Proxies.Soap; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; namespace Microsoft.Qwiq.Core.Tests { - public abstract class WorkItemStoreTests : ContextSpecification + public abstract class WorkItemStoreTests : ContextSpecification + where T : IWorkItemStore { protected IWorkItemStore WorkItemStore; internal MockQueryFactory QueryFactory; + protected abstract T Create(); + public override void Given() { - WorkItemStore = new WorkItemStoreProxy(null, null, QueryFactory); + WorkItemStore = Create(); + } + + public override void Cleanup() + { + WorkItemStore.Dispose(); + } + } + + public abstract class WorkItemStoreSoapTests : WorkItemStoreTests + { + protected override TfsSoap.WorkItemStoreProxy Create() + { + return new TfsSoap.WorkItemStoreProxy(null, null, QueryFactory); } } [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreTests + public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreSoapTests { private IEnumerable _actual; @@ -49,7 +66,7 @@ public void an_empty_result_set_is_returned() } [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreTests + public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreSoapTests { public override void Given() { @@ -76,7 +93,7 @@ public void a_query_string_with_one_id_is_generated() } [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreTests + public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreSoapTests { public override void Given() { @@ -103,7 +120,7 @@ public void a_query_string_with_two_ids_is_generated() } [TestClass] - public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreTests + public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreSoapTests { public override void Given() { diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index eb3f27f3..7cf9ea26 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -368,8 +368,8 @@ - + @@ -390,10 +390,6 @@ - - {1edeb333-3084-42bd-b273-4009b4b18541} - Qwiq.Linq - {d9ed32d7-03fa-468b-ad1a-249cef9c6cdb} Qwiq.Benchmark @@ -402,6 +398,10 @@ {8AC61B6E-BEC1-482D-A043-C65D2D343B35} Qwiq.Core + + {1edeb333-3084-42bd-b273-4009b4b18541} + Qwiq.Linq + {016e8d93-4195-4639-bcd5-77633e8e1681} Qwiq.Mapper diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index d926c3b7..239f76da 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -211,11 +211,11 @@ public string ReproSteps } } - public int Rev + public long Rev { get { - return (int)GetValue("Rev"); + return (long)GetValue("Rev"); } set { @@ -230,9 +230,9 @@ public DateTime RevisedDate set { SetValue("Revised Date", value); } } - public int Revision + public long Revision { - get { return (int)GetValue("Revision"); } + get { return (long)GetValue("Revision"); } set { SetValue("Revision", value); } } From 3576b0fe758cdda76015468b68fdccaf95c1dda4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 31 Jan 2017 19:35:08 -0800 Subject: [PATCH 002/251] Rough implementation consuming REST API Basic scenarios for querying by ID, IDs, or WIQL are implemented. Additional tests are still required to document / verify feature parity between the SOAP and REST implementations --- src/Qwiq.Core/ITfsTeamProjectCollection.cs | 2 + src/Qwiq.Core/IWorkItem.cs | 4 +- .../ItemAlreadyUpdatedOnServerException.cs | 2 + src/Qwiq.Core/LinkMapper.cs | 2 + .../Proxies/Rest/WorkItemLinkInfoProxy.cs | 27 ++ src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs | 257 +++++++++++++ .../Proxies/Rest/WorkItemStoreProxy.cs | 152 ++++++++ .../Proxies/Rest/WorkItemTypeProxy.cs | 25 ++ .../Proxies/{ => Soap}/AttachmentProxy.cs | 3 +- .../{ => Soap}/CommonStructureServiceProxy.cs | 4 +- .../{ => Soap}/FieldCollectionProxy.cs | 4 +- .../Proxies/{ => Soap}/FieldConflictProxy.cs | 2 +- .../Proxies/{ => Soap}/FieldProxy.cs | 2 +- .../Proxies/{ => Soap}/HyperlinkProxy.cs | 2 +- .../{ => Soap}/IdentityDescriptorProxy.cs | 2 +- .../IdentityManagementServiceProxy.cs | 4 +- .../Proxies/{ => Soap}/LinkCollectionProxy.cs | 4 +- src/Qwiq.Core/Proxies/{ => Soap}/LinkProxy.cs | 2 +- .../Proxies/{ => Soap}/NodeInfoProxy.cs | 2 +- src/Qwiq.Core/Proxies/{ => Soap}/NodeProxy.cs | 4 +- .../Proxies/{ => Soap}/ProjectInfoProxy.cs | 2 +- .../{ => Soap}/ProjectPropertyProxy.cs | 2 +- .../Proxies/{ => Soap}/ProjectProxy.cs | 4 +- .../Proxies/{ => Soap}/QueryProxy.cs | 3 +- .../Proxies/{ => Soap}/RelatedLinkProxy.cs | 3 +- .../Proxies/{ => Soap}/RevisionProxy.cs | 4 +- .../{ => Soap}/TeamFoundationIdentityProxy.cs | 4 +- .../{ => Soap}/WorkItemLinkInfoProxy.cs | 2 +- .../{ => Soap}/WorkItemLinkTypeEndProxy.cs | 3 +- .../{ => Soap}/WorkItemLinkTypeProxy.cs | 3 +- .../Proxies/{ => Soap}/WorkItemProxy.cs | 16 +- .../Proxies/{ => Soap}/WorkItemStoreProxy.cs | 9 +- .../Proxies/{ => Soap}/WorkItemTypeProxy.cs | 3 +- .../Proxies/TfsTeamProjectCollectionProxy.cs | 7 + src/Qwiq.Core/QueryFactory.cs | 53 ++- src/Qwiq.Core/Qwiq.Core.csproj | 55 +-- src/Qwiq.Core/WorkItemFields.cs | 12 + src/Qwiq.Core/WorkItemStoreFactory.cs | 55 ++- .../IdentityDescriptorProxyTests.cs | 1 + .../IdentityManagementServiceProxyTests.cs | 1 + test/Qwiq.Core.Tests/IntegrationTests.cs | 351 ++++++++++++++++++ test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 1 + test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 29 +- .../Qwiq.Mapper.Tests.csproj | 10 +- test/Qwiq.Mocks/MockWorkItem.cs | 8 +- 45 files changed, 1050 insertions(+), 97 deletions(-) create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs create mode 100644 src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs rename src/Qwiq.Core/Proxies/{ => Soap}/AttachmentProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/CommonStructureServiceProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/FieldCollectionProxy.cs (96%) rename src/Qwiq.Core/Proxies/{ => Soap}/FieldConflictProxy.cs (94%) rename src/Qwiq.Core/Proxies/{ => Soap}/FieldProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/HyperlinkProxy.cs (91%) rename src/Qwiq.Core/Proxies/{ => Soap}/IdentityDescriptorProxy.cs (93%) rename src/Qwiq.Core/Proxies/{ => Soap}/IdentityManagementServiceProxy.cs (98%) rename src/Qwiq.Core/Proxies/{ => Soap}/LinkCollectionProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/LinkProxy.cs (92%) rename src/Qwiq.Core/Proxies/{ => Soap}/NodeInfoProxy.cs (91%) rename src/Qwiq.Core/Proxies/{ => Soap}/NodeProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/ProjectInfoProxy.cs (91%) rename src/Qwiq.Core/Proxies/{ => Soap}/ProjectPropertyProxy.cs (89%) rename src/Qwiq.Core/Proxies/{ => Soap}/ProjectProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/QueryProxy.cs (95%) rename src/Qwiq.Core/Proxies/{ => Soap}/RelatedLinkProxy.cs (95%) rename src/Qwiq.Core/Proxies/{ => Soap}/RevisionProxy.cs (98%) rename src/Qwiq.Core/Proxies/{ => Soap}/TeamFoundationIdentityProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemLinkInfoProxy.cs (94%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemLinkTypeEndProxy.cs (96%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemLinkTypeProxy.cs (96%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemProxy.cs (97%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemStoreProxy.cs (95%) rename src/Qwiq.Core/Proxies/{ => Soap}/WorkItemTypeProxy.cs (94%) create mode 100644 src/Qwiq.Core/WorkItemFields.cs create mode 100644 test/Qwiq.Core.Tests/IntegrationTests.cs diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index 57dba2a6..323b285b 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -11,6 +11,8 @@ public interface ITfsTeamProjectCollection internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection, IDisposable { T GetService(); + + T GetClient(); } } diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 59fcc9ea..a5a75b91 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -91,7 +91,7 @@ public interface IWorkItem /// int RelatedLinkCount { get; } - int Rev { get; } + long Rev { get; } /// /// Gets a System.DateTime object that represents the revision date and time @@ -102,7 +102,7 @@ public interface IWorkItem /// /// Gets the integer that represents the revision number of this work item. /// - int Revision { get; } + long Revision { get; } /// /// Gets an object that represents a collection of valid revision numbers for this work diff --git a/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs b/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs index 6e028baa..35d75914 100644 --- a/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs +++ b/src/Qwiq.Core/ItemAlreadyUpdatedOnServerException.cs @@ -3,6 +3,8 @@ using System.Linq; using Microsoft.Qwiq.Exceptions; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq diff --git a/src/Qwiq.Core/LinkMapper.cs b/src/Qwiq.Core/LinkMapper.cs index 756d1a0c..e2f01b3e 100644 --- a/src/Qwiq.Core/LinkMapper.cs +++ b/src/Qwiq.Core/LinkMapper.cs @@ -1,6 +1,8 @@ using System; using Microsoft.Qwiq.Exceptions; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs new file mode 100644 index 00000000..21f8e4a0 --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs @@ -0,0 +1,27 @@ +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + public class WorkItemLinkInfoProxy : IWorkItemLinkInfo + { + private readonly WorkItemLink _item; + + internal WorkItemLinkInfoProxy(WorkItemLink item) + { + _item = item; + } + + public bool IsLocked => throw new NotImplementedException(); + + public int LinkTypeId => throw new NotImplementedException(); + + public int SourceId => (_item.Source?.Id).GetValueOrDefault(); + + public int TargetId => (_item.Target?.Id).GetValueOrDefault(); + } +} diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs new file mode 100644 index 00000000..10b1edb3 --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemProxy.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.TeamFoundation.WorkItemTracking.Client; +using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; + +using WorkItem = Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + public class WorkItemProxy : IWorkItem + { + private readonly WorkItem _item; + + internal WorkItemProxy(WorkItem item) + { + _item = item; + Type = new WorkItemTypeProxy(GetValue(CoreFieldRefNames.WorkItemType)); + Uri = new Uri(item.Url); + } + + public string AreaPath + { + get + { + return GetValue(CoreFieldRefNames.AreaPath); + } + set + { + SetValue(CoreFieldRefNames.AreaPath, value); + } + } + + public string AssignedTo + { + get + { + return GetValue(CoreFieldRefNames.AssignedTo); + } + set + { + SetValue(CoreFieldRefNames.AssignedTo, value); + } + } + + public int AttachedFileCount => GetValue(CoreFieldRefNames.AttachedFileCount); + + public IEnumerable Attachments { get { throw new NotImplementedException(); } } + + public string ChangedBy + { + get { return GetValue(CoreFieldRefNames.ChangedBy); } + set + { + SetValue(CoreFieldRefNames.ChangedBy, value); + } + } + + public DateTime ChangedDate + { + get { return (DateTime)GetValue(CoreFieldRefNames.ChangedDate); } + set + { + SetValue(CoreFieldRefNames.ChangedDate, value); + } + } + + public string CreatedBy + { + get { return GetValue(CoreFieldRefNames.CreatedBy); } + set + { + SetValue(CoreFieldRefNames.CreatedBy, value); + } + } + + public DateTime CreatedDate + { + get { return (DateTime)GetValue(CoreFieldRefNames.CreatedDate); } + set + { + SetValue(CoreFieldRefNames.CreatedDate, value); + } + } + + public string Description + { + get + { + return GetValue(CoreFieldRefNames.Description); + } + set + { + SetValue(CoreFieldRefNames.Description, value); + } + } + + public int ExternalLinkCount => GetValue(CoreFieldRefNames.ExternalLinkCount); + + public IFieldCollection Fields { get { throw new NotImplementedException(); } } + + public string History + { + get { return GetValue(CoreFieldRefNames.History) as string ?? string.Empty; } + set + { + SetValue(CoreFieldRefNames.History, value); + } + } + + public int HyperLinkCount => GetValue(CoreFieldRefNames.HyperLinkCount); + + public int Id => _item.Id.GetValueOrDefault(0); + + public bool IsDirty { get { throw new NotImplementedException(); } } + + public string IterationPath + { + get + { + return GetValue(CoreFieldRefNames.IterationPath); + } + set + { + SetValue(CoreFieldRefNames.IterationPath, value); + } + } + + public string Keywords + { + get { return GetValue(WorkItemFields.Keywords); } + set { SetValue(WorkItemFields.Keywords, value); } + } + + public ICollection Links { get { throw new NotImplementedException(); } } + + public int RelatedLinkCount => GetValue(CoreFieldRefNames.RelatedLinkCount); + + public long Rev => GetValue(CoreFieldRefNames.Rev); + + public DateTime RevisedDate => GetValue(CoreFieldRefNames.RevisedDate); + + public long Revision => Rev; + + public IEnumerable Revisions { get { throw new NotImplementedException(); } } + + public string State + { + get { return GetValue(CoreFieldRefNames.State); } + set { SetValue(CoreFieldRefNames.State, value); } + } + + public string Tags + { + get { return GetValue(CoreFieldRefNames.Tags); } + set { SetValue(CoreFieldRefNames.Tags, value); } + } + + public string Title + { + get { return GetValue(CoreFieldRefNames.Title); } + set { SetValue(CoreFieldRefNames.Title, value); } + } + + public IWorkItemType Type { get; } + + public Uri Uri { get; } + + public object this[string name] + { + get + { + return GetValue(name); + } + set + { + SetValue(name, value); + } + } + + public void Close() + { + } + + public IWorkItem Copy() + { + throw new NotImplementedException(); + } + + public IHyperlink CreateHyperlink(string location) + { + throw new NotImplementedException(); + } + + public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) + { + throw new NotImplementedException(); + } + + public bool IsValid() + { + throw new NotImplementedException(); + } + + public void Open() + { + } + + public void PartialOpen() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + + public void Save() + { + throw new NotImplementedException(); + } + + public void Save(SaveFlags saveFlags) + { + throw new NotImplementedException(); + } + + public IEnumerable Validate() + { + throw new NotImplementedException(); + } + + private T GetValue(string field) + { + return (T)GetValue(field); + } + + private object GetValue(string field) + { + if (!_item.Fields.TryGetValue(field, out object val)) + { + // To preserve OM compatability + throw new FieldDefinitionNotExistException( + $"TF26026: A field definition ID {field} in the work item type definition file does not exist. Add a definition for this field ID, or remove the reference to the field ID and try again."); + } + return val; + } + + private void SetValue(string field, object value) + { + _item.Fields[field] = value; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs new file mode 100644 index 00000000..553ade1a --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemStoreProxy.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Qwiq.Exceptions; +using Microsoft.Qwiq.Proxies.Soap; +using Microsoft.TeamFoundation.WorkItemTracking.Client.Wiql; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + + + public class WorkItemStoreProxy : IWorkItemStore + { + private IInternalTfsTeamProjectCollection _teamProjectCollection; + + private readonly WorkItemTrackingHttpClient _workItemStore; + + + + internal WorkItemStoreProxy( + IInternalTfsTeamProjectCollection teamProjectCollection, + WorkItemTrackingHttpClient workItemStore) + { + if (teamProjectCollection == null) throw new ArgumentNullException(nameof(teamProjectCollection)); + if (workItemStore == null) throw new ArgumentNullException(nameof(workItemStore)); + + _teamProjectCollection = teamProjectCollection; + _workItemStore = workItemStore; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + _teamProjectCollection?.Dispose(); + _teamProjectCollection = null; + } + } + + public IEnumerable Query(string wiql, bool dayPrecision = false) + { + if (dayPrecision) throw new NotSupportedException(); + + var p = Parser.ParseSyntax(wiql); + var w = new Wiql() { Query = p.ToString() }; + + var fields = new List(); + for (var i = 0; i < p.Fields.Count; i++) + { + var field = p.Fields[i]; + fields.Add(field.Value); + } + + + var result = _workItemStore.QueryByWiqlAsync(w, "OS").GetAwaiter().GetResult(); + if (result.WorkItems.Any()) + { + int skip = 0; + const int BatchSize = 100; + IEnumerable workItemRefs; + do + { + workItemRefs = result.WorkItems.Skip(skip).Take(BatchSize); + if (workItemRefs.Any()) + { + // TODO: Support AsOf + var workItems = _workItemStore.GetWorkItemsAsync(workItemRefs.Select(wir => wir.Id), fields,null, null).GetAwaiter().GetResult(); + foreach (var workItem in workItems) + { + yield return new WorkItemProxy(workItem); + } + } + skip += BatchSize; + } + while (workItemRefs.Count() == BatchSize); + } + } + private static IEnumerable Ids(WorkItemQueryResult result, int skip = 0) + { + return result.WorkItemRelations.Where(r => r.Target != null).Select(r => r.Target.Id) + .Union(result.WorkItemRelations.Where(r => r.Source != null).Select(r => r.Source.Id)) + .Skip(skip) + .Take(100); + } + + public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) + { + if (dayPrecision) throw new NotSupportedException(); + + var w = new Wiql() { Query = wiql }; + + var result = _workItemStore.QueryByWiqlAsync(w, "OS").GetAwaiter().GetResult(); + foreach (var workItem in result.WorkItemRelations) + { + yield return new WorkItemLinkInfoProxy(workItem); + } + } + + public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + { + if (!ids.Any()) + { + yield return null; + } + + var wis = _workItemStore.GetWorkItemsAsync(ids, null, asOf, WorkItemExpand.Fields).GetAwaiter().GetResult(); + foreach (var workItem in wis) + { + // write work item to console + yield return new WorkItemProxy(workItem); + } + } + + public IWorkItem Query(int id, DateTime? asOf = null) + { + var wi = _workItemStore.GetWorkItemAsync(id, null, asOf, WorkItemExpand.Fields).GetAwaiter().GetResult(); + return new WorkItemProxy(wi); + } + + public ITfsTeamProjectCollection TeamProjectCollection => _teamProjectCollection; + + public IEnumerable Projects + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable WorkItemLinkTypes { get; } + + public TimeZone TimeZone + { + get + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs new file mode 100644 index 00000000..888ca5d0 --- /dev/null +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemTypeProxy.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Qwiq.Proxies.Rest +{ + public class WorkItemTypeProxy : IWorkItemType + { + internal WorkItemTypeProxy(string name) + { + Name = name; + } + + public string Description { get; } + + public string Name { get; } + + public IWorkItem NewWorkItem() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Qwiq.Core/Proxies/AttachmentProxy.cs b/src/Qwiq.Core/Proxies/Soap/AttachmentProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/AttachmentProxy.cs rename to src/Qwiq.Core/Proxies/Soap/AttachmentProxy.cs index 84d7381a..bdc885d6 100644 --- a/src/Qwiq.Core/Proxies/AttachmentProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/AttachmentProxy.cs @@ -1,7 +1,8 @@ using System; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class AttachmentProxy : IAttachment { diff --git a/src/Qwiq.Core/Proxies/CommonStructureServiceProxy.cs b/src/Qwiq.Core/Proxies/Soap/CommonStructureServiceProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/CommonStructureServiceProxy.cs rename to src/Qwiq.Core/Proxies/Soap/CommonStructureServiceProxy.cs index 7b3bf46f..243058a4 100644 --- a/src/Qwiq.Core/Proxies/CommonStructureServiceProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/CommonStructureServiceProxy.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Xml; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class CommonStructureServiceProxy : ICommonStructureService { diff --git a/src/Qwiq.Core/Proxies/FieldCollectionProxy.cs b/src/Qwiq.Core/Proxies/Soap/FieldCollectionProxy.cs similarity index 96% rename from src/Qwiq.Core/Proxies/FieldCollectionProxy.cs rename to src/Qwiq.Core/Proxies/Soap/FieldCollectionProxy.cs index 9ed9952a..3b1776db 100644 --- a/src/Qwiq.Core/Proxies/FieldCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/FieldCollectionProxy.cs @@ -1,10 +1,12 @@ using System.Collections; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { internal class FieldCollectionProxy : IFieldCollection { diff --git a/src/Qwiq.Core/Proxies/FieldConflictProxy.cs b/src/Qwiq.Core/Proxies/Soap/FieldConflictProxy.cs similarity index 94% rename from src/Qwiq.Core/Proxies/FieldConflictProxy.cs rename to src/Qwiq.Core/Proxies/Soap/FieldConflictProxy.cs index 48f28a68..236eb82e 100644 --- a/src/Qwiq.Core/Proxies/FieldConflictProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/FieldConflictProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class FieldConflictProxy : IFieldConflict { diff --git a/src/Qwiq.Core/Proxies/FieldProxy.cs b/src/Qwiq.Core/Proxies/Soap/FieldProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/FieldProxy.cs rename to src/Qwiq.Core/Proxies/Soap/FieldProxy.cs index 79f92f5e..cc79b6d1 100644 --- a/src/Qwiq.Core/Proxies/FieldProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/FieldProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class FieldProxy : IField { diff --git a/src/Qwiq.Core/Proxies/HyperlinkProxy.cs b/src/Qwiq.Core/Proxies/Soap/HyperlinkProxy.cs similarity index 91% rename from src/Qwiq.Core/Proxies/HyperlinkProxy.cs rename to src/Qwiq.Core/Proxies/Soap/HyperlinkProxy.cs index 3ae39357..b05f0dcb 100644 --- a/src/Qwiq.Core/Proxies/HyperlinkProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/HyperlinkProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class HyperlinkProxy : LinkProxy, IHyperlink { diff --git a/src/Qwiq.Core/Proxies/IdentityDescriptorProxy.cs b/src/Qwiq.Core/Proxies/Soap/IdentityDescriptorProxy.cs similarity index 93% rename from src/Qwiq.Core/Proxies/IdentityDescriptorProxy.cs rename to src/Qwiq.Core/Proxies/Soap/IdentityDescriptorProxy.cs index 26dbcf5d..489100de 100644 --- a/src/Qwiq.Core/Proxies/IdentityDescriptorProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/IdentityDescriptorProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Framework.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class IdentityDescriptorProxy : IIdentityDescriptor { diff --git a/src/Qwiq.Core/Proxies/IdentityManagementServiceProxy.cs b/src/Qwiq.Core/Proxies/Soap/IdentityManagementServiceProxy.cs similarity index 98% rename from src/Qwiq.Core/Proxies/IdentityManagementServiceProxy.cs rename to src/Qwiq.Core/Proxies/Soap/IdentityManagementServiceProxy.cs index 2c814653..79068806 100644 --- a/src/Qwiq.Core/Proxies/IdentityManagementServiceProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/IdentityManagementServiceProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.Framework; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class IdentityManagementServiceProxy : IIdentityManagementService { diff --git a/src/Qwiq.Core/Proxies/LinkCollectionProxy.cs b/src/Qwiq.Core/Proxies/Soap/LinkCollectionProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/LinkCollectionProxy.cs rename to src/Qwiq.Core/Proxies/Soap/LinkCollectionProxy.cs index aad0e81f..c11c0b4b 100644 --- a/src/Qwiq.Core/Proxies/LinkCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/LinkCollectionProxy.cs @@ -1,10 +1,10 @@ -using System; using System.Collections; using System.Collections.Generic; using System.Linq; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class LinkCollectionProxy : ICollection { diff --git a/src/Qwiq.Core/Proxies/LinkProxy.cs b/src/Qwiq.Core/Proxies/Soap/LinkProxy.cs similarity index 92% rename from src/Qwiq.Core/Proxies/LinkProxy.cs rename to src/Qwiq.Core/Proxies/Soap/LinkProxy.cs index 4c7ee8c3..d5dcc308 100644 --- a/src/Qwiq.Core/Proxies/LinkProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/LinkProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class LinkProxy : ILink { diff --git a/src/Qwiq.Core/Proxies/NodeInfoProxy.cs b/src/Qwiq.Core/Proxies/Soap/NodeInfoProxy.cs similarity index 91% rename from src/Qwiq.Core/Proxies/NodeInfoProxy.cs rename to src/Qwiq.Core/Proxies/Soap/NodeInfoProxy.cs index 15085a7f..eea08e63 100644 --- a/src/Qwiq.Core/Proxies/NodeInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/NodeInfoProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class NodeInfoProxy : INodeInfo { diff --git a/src/Qwiq.Core/Proxies/NodeProxy.cs b/src/Qwiq.Core/Proxies/Soap/NodeProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/NodeProxy.cs rename to src/Qwiq.Core/Proxies/Soap/NodeProxy.cs index 3b534837..792c5654 100644 --- a/src/Qwiq.Core/Proxies/NodeProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/NodeProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class NodeProxy : INode { diff --git a/src/Qwiq.Core/Proxies/ProjectInfoProxy.cs b/src/Qwiq.Core/Proxies/Soap/ProjectInfoProxy.cs similarity index 91% rename from src/Qwiq.Core/Proxies/ProjectInfoProxy.cs rename to src/Qwiq.Core/Proxies/Soap/ProjectInfoProxy.cs index 7e5e6a12..1a41070a 100644 --- a/src/Qwiq.Core/Proxies/ProjectInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/ProjectInfoProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class ProjectInfoProxy : IProjectInfo { diff --git a/src/Qwiq.Core/Proxies/ProjectPropertyProxy.cs b/src/Qwiq.Core/Proxies/Soap/ProjectPropertyProxy.cs similarity index 89% rename from src/Qwiq.Core/Proxies/ProjectPropertyProxy.cs rename to src/Qwiq.Core/Proxies/Soap/ProjectPropertyProxy.cs index d6b308ee..3907d09b 100644 --- a/src/Qwiq.Core/Proxies/ProjectPropertyProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/ProjectPropertyProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class ProjectPropertyProxy : IProjectProperty { diff --git a/src/Qwiq.Core/Proxies/ProjectProxy.cs b/src/Qwiq.Core/Proxies/Soap/ProjectProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/ProjectProxy.cs rename to src/Qwiq.Core/Proxies/Soap/ProjectProxy.cs index e8ed2031..74791b6e 100644 --- a/src/Qwiq.Core/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/ProjectProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class ProjectProxy : IProject { diff --git a/src/Qwiq.Core/Proxies/QueryProxy.cs b/src/Qwiq.Core/Proxies/Soap/QueryProxy.cs similarity index 95% rename from src/Qwiq.Core/Proxies/QueryProxy.cs rename to src/Qwiq.Core/Proxies/Soap/QueryProxy.cs index 627b8ed5..5607eea1 100644 --- a/src/Qwiq.Core/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/QueryProxy.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class QueryProxy : IQuery { diff --git a/src/Qwiq.Core/Proxies/RelatedLinkProxy.cs b/src/Qwiq.Core/Proxies/Soap/RelatedLinkProxy.cs similarity index 95% rename from src/Qwiq.Core/Proxies/RelatedLinkProxy.cs rename to src/Qwiq.Core/Proxies/Soap/RelatedLinkProxy.cs index 8fc3126b..fe1b8ff1 100644 --- a/src/Qwiq.Core/Proxies/RelatedLinkProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/RelatedLinkProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class RelatedLinkProxy : LinkProxy, IRelatedLink { diff --git a/src/Qwiq.Core/Proxies/RevisionProxy.cs b/src/Qwiq.Core/Proxies/Soap/RevisionProxy.cs similarity index 98% rename from src/Qwiq.Core/Proxies/RevisionProxy.cs rename to src/Qwiq.Core/Proxies/Soap/RevisionProxy.cs index 909ef1b5..6f23f4d9 100644 --- a/src/Qwiq.Core/Proxies/RevisionProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/RevisionProxy.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { /// /// Wrapper around the TFS RevisionProxy. This exists so that every agent doesn't need to reference diff --git a/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs b/src/Qwiq.Core/Proxies/Soap/TeamFoundationIdentityProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs rename to src/Qwiq.Core/Proxies/Soap/TeamFoundationIdentityProxy.cs index 2caf910a..938d5d16 100644 --- a/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/TeamFoundationIdentityProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.Framework.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class TeamFoundationIdentityProxy : ITeamFoundationIdentity { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkInfoProxy.cs similarity index 94% rename from src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemLinkInfoProxy.cs index aa51dbe2..7530235a 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkInfoProxy.cs @@ -1,6 +1,6 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemLinkInfoProxy : IWorkItemLinkInfo { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeEndProxy.cs similarity index 96% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeEndProxy.cs index 1585fd51..6c3d44d8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeEndProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeProxy.cs similarity index 96% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeProxy.cs index ff55ecfa..7d975459 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemLinkTypeProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemLinkTypeProxy : IWorkItemLinkType { diff --git a/src/Qwiq.Core/Proxies/WorkItemProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemProxy.cs similarity index 97% rename from src/Qwiq.Core/Proxies/WorkItemProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemProxy.cs index 4941bde0..e1b17234 100644 --- a/src/Qwiq.Core/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { /// /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference @@ -21,8 +23,8 @@ internal WorkItemProxy(Tfs.WorkItem item) public string AssignedTo { - get { return _item["Assigned To"].ToString(); } - set { _item["Assigned To"] = value; } + get { return _item[Tfs.CoreFieldReferenceNames.AssignedTo].ToString(); } + set { _item[Tfs.CoreFieldReferenceNames.AssignedTo] = value; } } /// @@ -180,7 +182,7 @@ public DateTime RevisedDate /// /// Gets the integer that represents the revision number of this work item. /// - public int Revision + public long Revision { get { return _item.Revision; } } @@ -211,8 +213,8 @@ public string Tags public string Keywords { - get { return (string)_item["Keywords"]; } - set { _item["Keywords"] = value; } + get { return (string)_item[WorkItemFields.Keywords]; } + set { _item[WorkItemFields.Keywords] = value; } } /// @@ -244,7 +246,7 @@ public Uri Uri get { return _item.Uri; } } - public int Rev + public long Rev { get { return _item.Rev; } } diff --git a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemStoreProxy.cs similarity index 95% rename from src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemStoreProxy.cs index 90f77969..1bcf8d6c 100644 --- a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemStoreProxy.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { /// /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference @@ -14,7 +16,7 @@ namespace Microsoft.Qwiq.Proxies public class WorkItemStoreProxy : IWorkItemStore { private readonly IQueryFactory _queryFactory; - private readonly IInternalTfsTeamProjectCollection _tfs; + private IInternalTfsTeamProjectCollection _tfs; private readonly TfsWorkItem.WorkItemStore _workItemStore; internal WorkItemStoreProxy(IInternalTfsTeamProjectCollection tfs, TfsWorkItem.WorkItemStore workItemStore, IQueryFactory queryFactory) @@ -36,7 +38,8 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _tfs.Dispose(); + _tfs?.Dispose(); + _tfs = null; } } #endregion diff --git a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core/Proxies/Soap/WorkItemTypeProxy.cs similarity index 94% rename from src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs rename to src/Qwiq.Core/Proxies/Soap/WorkItemTypeProxy.cs index ec64e813..f30de7f8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/Soap/WorkItemTypeProxy.cs @@ -1,7 +1,8 @@ using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq.Proxies.Soap { public class WorkItemTypeProxy : IWorkItemType { diff --git a/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs b/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs index 0ad893c6..2b7400a9 100644 --- a/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs @@ -1,5 +1,7 @@ using System; using Microsoft.Qwiq.Exceptions; +using Microsoft.Qwiq.Proxies.Soap; + using Tfs = Microsoft.TeamFoundation; namespace Microsoft.Qwiq.Proxies @@ -31,6 +33,11 @@ public T GetService() return _tfs.GetService(); } + public T GetClient() + { + return _tfs.GetClient(); + } + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/src/Qwiq.Core/QueryFactory.cs b/src/Qwiq.Core/QueryFactory.cs index 9d43db9d..e07719bd 100644 --- a/src/Qwiq.Core/QueryFactory.cs +++ b/src/Qwiq.Core/QueryFactory.cs @@ -1,5 +1,8 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq @@ -9,23 +12,51 @@ internal interface IQueryFactory IQuery Create(string wiql, bool dayPrecision); } - internal class QueryFactory : IQueryFactory + namespace Microsoft.Qwiq.Soap { - private readonly Tfs.WorkItemStore _store; - - private QueryFactory(Tfs.WorkItemStore store) + internal class QueryFactory : IQueryFactory { - _store = store; - } + private readonly Tfs.WorkItemStore _store; - public static QueryFactory GetInstance(Tfs.WorkItemStore store) - { - return new QueryFactory(store); + private QueryFactory(Tfs.WorkItemStore store) + { + _store = store; + } + + public static IQueryFactory GetInstance(Tfs.WorkItemStore store) + { + return new QueryFactory(store); + } + + public IQuery Create(string wiql, bool dayPrecision) + { + return + ExceptionHandlingDynamicProxyFactory.Create( + new QueryProxy(new Tfs.Query(_store, wiql, null, dayPrecision))); + } } + } - public IQuery Create(string wiql, bool dayPrecision) + namespace Microsoft.Qwiq.Rest + { + internal class QueryFactory : IQueryFactory { - return ExceptionHandlingDynamicProxyFactory.Create(new QueryProxy(new Tfs.Query(_store, wiql, null, dayPrecision))); + private readonly WorkItemTrackingHttpClient _store; + + private QueryFactory(WorkItemTrackingHttpClient store) + { + _store = store; + } + + public static IQueryFactory GetInstance(WorkItemTrackingHttpClient store) + { + return new QueryFactory(store); + } + + public IQuery Create(string wiql, bool dayPrecision) + { + throw new System.NotImplementedException(); + } } } } diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 584bba9f..75e59ee1 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -287,38 +287,43 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + diff --git a/src/Qwiq.Core/WorkItemFields.cs b/src/Qwiq.Core/WorkItemFields.cs new file mode 100644 index 00000000..8444baa8 --- /dev/null +++ b/src/Qwiq.Core/WorkItemFields.cs @@ -0,0 +1,12 @@ +using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; + +namespace Microsoft.Qwiq +{ + public static class WorkItemFields + { + public const string Keywords = "Microsoft.VSTS.Common.Keywords"; + } + + + +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemStoreFactory.cs b/src/Qwiq.Core/WorkItemStoreFactory.cs index 8ed74a22..89002307 100644 --- a/src/Qwiq.Core/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core/WorkItemStoreFactory.cs @@ -3,21 +3,32 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; + using Microsoft.Qwiq.Proxies; +using TfsSoap = Microsoft.Qwiq.Proxies.Soap; +using TfsRest = Microsoft.Qwiq.Proxies.Rest; using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Build.WebApi; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.WorkItemTracking.Client; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; namespace Microsoft.Qwiq { public interface IWorkItemStoreFactory { - IWorkItemStore Create(Uri endpoint, TfsCredentials credentials); + IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default); + + IWorkItemStore Create(Uri endpoint, IEnumerable credentials, ClientType type = ClientType.Default); + } - IWorkItemStore Create(Uri endpoint, IEnumerable credentials); + public enum ClientType + { + Default, + Soap, + Rest } - + public class WorkItemStoreFactory : IWorkItemStoreFactory { private static readonly Lazy Instance = new Lazy(() => new WorkItemStoreFactory()); @@ -31,14 +42,14 @@ public static IWorkItemStoreFactory GetInstance() return Instance.Value; } - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) + public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default) { - return Create(endpoint, new [] { credentials }); + return Create(endpoint, new[] { credentials }, type); } - public IWorkItemStore Create(Uri endpoint, IEnumerable credentials) + public IWorkItemStore Create(Uri endpoint, IEnumerable credentials, ClientType type = ClientType.Default) { - Func queryFactoryFunc = QueryFactory.GetInstance; + foreach (var credential in credentials) { try @@ -48,10 +59,20 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia System.Diagnostics.Trace.TraceInformation("TFS connection attempt success with {0}/{1}.", credential.Credentials.Windows.GetType(), credential.Credentials.Federated.GetType()); var tfs = ExceptionHandlingDynamicProxyFactory.Create(new TfsTeamProjectCollectionProxy(tfsNative)); - var workItemStore = tfs.GetService(); - var queryFactory = queryFactoryFunc.Invoke(workItemStore); - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemStoreProxy(tfs, workItemStore, queryFactory)); + IWorkItemStore wis = null; + switch (type) + { + case ClientType.Rest: + wis = CreateRestWorkItemStore(tfs); + break; + case ClientType.Soap: + case ClientType.Default: + wis = CreateSoapWorkItemStore(tfs); + break; + } + + return ExceptionHandlingDynamicProxyFactory.Create(wis); } catch (TeamFoundationServerUnauthorizedException e) { @@ -63,6 +84,20 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia throw new AccessDeniedException("Invalid credentials"); } + private static IWorkItemStore CreateRestWorkItemStore(IInternalTfsTeamProjectCollection tfs) + { + var workItemStore = tfs.GetClient(); + return new TfsRest.WorkItemStoreProxy(tfs, workItemStore); + } + + private static IWorkItemStore CreateSoapWorkItemStore(IInternalTfsTeamProjectCollection tfs) + { + + var workItemStore = tfs.GetService(); + var queryFactory = Microsoft.Qwiq.Soap.QueryFactory.GetInstance(workItemStore); + return new TfsSoap.WorkItemStoreProxy(tfs, workItemStore, queryFactory); + } + private static TfsTeamProjectCollection ConnectToTfsCollection( Uri endpoint, TfsClientCredentials credentials) diff --git a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs index 70d7caa7..7cc6936d 100644 --- a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs @@ -1,4 +1,5 @@ using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; diff --git a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs index 511e1fa2..b83f4f7c 100644 --- a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs @@ -2,6 +2,7 @@ using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Proxies.Soap; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; diff --git a/test/Qwiq.Core.Tests/IntegrationTests.cs b/test/Qwiq.Core.Tests/IntegrationTests.cs new file mode 100644 index 00000000..d4b0a73c --- /dev/null +++ b/test/Qwiq.Core.Tests/IntegrationTests.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Core.Tests +{ + [TestClass] + public class SingleIdTests : IntegrationContextSpecification + { + private const int Id = 10726528; + + public override void When() + { + SoapResult.WorkItem = SoapResult.WorkItemStore.Query(Id); + RestResult.WorkItem = RestResult.WorkItemStore.Query(Id); + } + } + + [TestClass] + public class MultipleIdTests : IntegrationContextSpecification + { + private const int Id = 10726528; + + public override void When() + { + SoapResult.WorkItem = SoapResult.WorkItemStore.Query(new[] { Id }).Single(); + RestResult.WorkItem = RestResult.WorkItemStore.Query(new[] { Id }).Single(); + } + } + + [TestClass] + public class WiqlFlatQueryTests : IntegrationContextSpecification + { + private const int Id = 10726528; + + private static readonly string Wiql = $"SELECT {string.Join(", ", CoreFields)} FROM WorkItems WHERE [System.Id] = {Id}"; + + public override void When() + { + RestResult.WorkItem = RestResult.WorkItemStore.Query(Wiql).Single(); + SoapResult.WorkItem = SoapResult.WorkItemStore.Query(Wiql).Single(); + } + } + + [TestClass] + public class LargeWiqlHierarchyQueryTests : ContextSpecification + { + protected Result RestResult { get; set; } + + protected Result SoapResult { get; set; } + + public override void Given() + { + var credentials = Credentials.CredentialsFactory.CreateCredentials(); + var fac = WorkItemStoreFactory.GetInstance(); + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; + RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + } + + public override void When() + { + const string WIQL = @" +SELECT [Microsoft.VSTS.CMMI.TaskType], [System.Reason], [System.Id], [Priority], [OSG.Rank], [Microsoft.VSTS.Scheduling.OriginalEstimate], [OSG.QualityEstimate], [OSG.DevEstimate], [OSG.Cost], [OSG.RemainingDays], [System.Title], [System.State], [Microsoft.VSTS.Common.Release], [Microsoft.VSTS.Common.Keywords], [System.IterationPath], [System.AreaPath], [System.WorkItemType], [OSG.Product] +FROM WorkItemLinks +WHERE + Source.[System.Id] IN (164972, 306417, 710095, 3135398, 3249369, 3707824, 4233618, 4708876, 4774628, 4774633, 4858832, 5050537, 5080053, 5321076, 5347847, 5348414, 5399017, 5399125, 5399142, 5404813, 5503336, 5570261, 5580845, 5639025, 5662931, 5674580, 5755004, 5779431, 5791052, 5810289, 6251354, 6418471, 6420861, 6966852, 7138009, 7266472, 7358985, 7417629, 7418003, 7418008, 7475634, 7529329, 7547051, 7547912, 7578388, 7710163, 7726674, 7734221, 7734222, 7734223, 7734225, 7734226, 7734229, 7734230, 7734231, 7734232, 7734234, 7734239, 7734240, 7734242, 7734245, 7734246, 7734247, 7734252, 7734254, 7734255, 7734258, 7734260, 7734261, 7734262, 7734263, 7734264, 7734267, 7734268, 7734269, 7762780, 7802385, 7825004, 7879923, 7881302, 7889153, 7899054, 7899115, 7899207, 7899214, 7899226, 7924439, 7925256, 7925264, 7925414, 7925435, 7978728, 7989044, 7989565, 8008196, 8029597, 8062462, 8062582, 8062597, 8083468, 8112721, 8150555, 8165162, 8165174, 8165211, 8167621, 8167748, 8168041, 8168698, 8169478, 8169633, 8181048, 8217827, 8231545, 8232318, 8240155, 8249590, 8249712, 8269111, 8273089, 8273096, 8273195, 8291862, 8291899, 8305945, 8311776, 8328982, 8329001, 8329069, 8329093, 8329134, 8329157, 8329534, 8329644, 8329835, 8330248, 8339386, 8339426, 8339444, 8340276, 8402025, 8403861, 8415967, 8461788, 8461811, 8464765, 8478429, 8478988, 8479926, 8511916, 8521180, 8523040, 8525637, 8550287, 8584490, 8629612, 8629628, 8647508, 8704880, 8771767, 8771853, 8889742, 8974486, 8975430, 8975466, 8975520, 8975657, 9043259, 9197486, 9197751, 9202677, 9204728, 9279773, 9280064, 9403077, 9403332, 9403384, 9403462, 9405201, 9406323, 9411986, 9416107, 9416388, 9416413, 9416498, 9416502, 9416513, 9416557, 9416562, 9416600, 9416619, 9416637, 9416710, 9416724, 9416744, 9416752, 9416768, 9416800, 9445547, 9457647, 9459369, 9459387, 9459443, 9460109, 9460216, 9460225, 9460246, 9460256, 9460263, 9492546, 9498278, 9506510, 9506522, 9506571, 9506641, 9637927, 9644862, 9647371, 9718740, 9780582, 9796760, 9810502, 9831841, 9836943, 10013800, 10033936, 10211640, 10277871, 10397497, 10429016, 10448096, 10471271, 10471325, 10513555, 10527610, 10530029, 10530447, 10530854, 10562556, 10588201, 10597347, 10597392, 10598841, 10608217, 10621199, 10621202, 10621236, 10621302, 10650244, 10693987, 10694717, 10696544, 10696590, 10696618, 10696638, 10726461, 10726528, 10726538, 10726549, 10726569, 10726584, 10726595, 10726623, 10726641, 10731466, 10732765, 10735376, 10741454, 10742199, 10748682, 10753466, 10754027, 10760613, 10760657, 10760716, 10760745, 10760797, 10761005, 10764882, 10769936, 10769985, 10770155, 10779176, 10779780, 10780103, 10780404, 10781600, 10781696, 10782702, 10783011, 10784489, 10786336, 10786381, 10788814, 10788938, 10788942, 10788980, 10788991, 10789027, 10789081, 10789087, 10789109, 10789112, 10789116, 10789118, 10789122, 10789177, 10789263, 10789737, 10795841, 10796505, 10797459, 10799250, 10799746, 10801935, 10802569, 10803778, 10804358, 10804374, 10804396, 10804611, 10804706, 10824786, 10824794, 10824844, 10825656, 10835610, 10835947, 10836042, 10836468, 10838442, 10838469, 10838487, 10847341, 10851434, 10870376, 10870428, 10870517, 10871100, 10872787, 10872818, 10872890, 10872906, 10872957, 10872990) + AND [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' + AND Target.[System.AreaPath] UNDER 'OS\Core\WebPlat' + AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', 'Deliverable', 'Task') +mode(Recursive) +"; + + RestResult.WorkItemLinks = RestResult.WorkItemStore.QueryLinks(WIQL).ToList(); + SoapResult.WorkItemLinks = SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(); + } + + public override void Cleanup() + { + SoapResult?.Dispose(); + RestResult?.Dispose(); + } + + protected class Result : IDisposable + { + public IWorkItem WorkItem { get; set; } + + public IEnumerable WorkItemLinks { get; set; } + + public IWorkItemStore WorkItemStore { get; set; } + + public void Dispose() + { + WorkItemStore?.Dispose(); + } + } + + [TestMethod] + public void BigTest() + { + } + } + + [TestClass] + public class WiqlHierarchyQueryTests : ContextSpecification + { + protected Result RestResult { get; set; } + + protected Result SoapResult { get; set; } + + public override void Given() + { + var credentials = Credentials.CredentialsFactory.CreateCredentials(); + var fac = WorkItemStoreFactory.GetInstance(); + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; + RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + } + + public override void When() + { + const string WIQL = @" +SELECT * +FROM WorkItemLinks +WHERE + [Source].[System.TeamProject] = 'OS' AND + [Source].[System.ID] = 10726528 AND + [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' AND + [Target].[System.WorkItemType] = 'Scenario' +mode(recursive) +"; + + RestResult.WorkItemLinks = RestResult.WorkItemStore.QueryLinks(WIQL).ToList(); + SoapResult.WorkItemLinks = SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(); + } + + public override void Cleanup() + { + SoapResult?.Dispose(); + RestResult?.Dispose(); + } + + [TestMethod] + public void SOAP_Links_returned() + { + SoapResult.WorkItemLinks.ShouldNotBeNull(); + } + + [TestMethod] + public void REST_Links_returned() + { + RestResult.WorkItemLinks.ShouldNotBeNull(); + } + + [TestMethod] + public void Same_number_of_links_returned() + { + RestResult.WorkItemLinks.Count().ShouldEqual(SoapResult.WorkItemLinks.Count()); + } + + [TestMethod] + public void WorkItemLink_SourceId_TargetId_are_equal() + { + for (var i = 0; i < RestResult.WorkItemLinks.Count(); i++) + { + var r = RestResult.WorkItemLinks.ElementAt(i); + var s = SoapResult.WorkItemLinks.ElementAt(i); + + r.SourceId.ShouldEqual(s.SourceId); + r.TargetId.ShouldEqual(s.TargetId); + } + } + + protected class Result : IDisposable + { + public IWorkItem WorkItem { get; set; } + + public IEnumerable WorkItemLinks { get; set; } + + public IWorkItemStore WorkItemStore { get; set; } + + public void Dispose() + { + WorkItemStore?.Dispose(); + } + } + } + + + public abstract class IntegrationContextSpecification : ContextSpecification + { + protected Result RestResult { get; set; } + + protected Result SoapResult { get; set; } + + [TestMethod] + public void AreaPath_is_equal() + { + RestResult.WorkItem.AreaPath.ShouldEqual(SoapResult.WorkItem.AreaPath); + } + + [TestMethod] + public void AssignedTo_is_equal() + { + RestResult.WorkItem.AssignedTo.ShouldEqual(SoapResult.WorkItem.AssignedTo); + } + + [TestMethod] + public void ChangedBy_is_equal() + { + RestResult.WorkItem.ChangedBy.ShouldEqual(SoapResult.WorkItem.ChangedBy); + } + + [TestMethod] + public void ChangedDate_is_equal() + { + RestResult.WorkItem.ChangedDate.ShouldEqual(SoapResult.WorkItem.ChangedDate.ToLocalTime()); + } + + public override void Cleanup() + { + SoapResult?.Dispose(); + RestResult?.Dispose(); + } + + protected static readonly string[] CoreFields = + { + "System.AreaPath", "System.AssignedTo", "System.AttachedFileCount", "System.ChangedBy", + "System.ChangedDate", "System.CreatedBy", "System.CreatedDate", "System.Description", + "System.ExternalLinkCount", "System.History", "System.HyperLinkCount", "System.Id", + "System.IterationPath", "System.RelatedLinkCount", "System.Rev", "System.RevisedDate", + "System.State", "System.Title", "System.WorkItemType", + }; + + [TestMethod] + public void CoreFields_are_equal() + { + + + foreach (var field in CoreFields) + { + RestResult.WorkItem[field].ShouldEqual(SoapResult.WorkItem[field]); + } + } + + [TestMethod] + public void CreatedBy_is_equal() + { + RestResult.WorkItem.CreatedBy.ShouldEqual(SoapResult.WorkItem.CreatedBy); + } + + [TestMethod] + public void Rev_is_equal() + { + RestResult.WorkItem.Rev.ShouldEqual(SoapResult.WorkItem.Rev); + } + + [TestMethod] + public void CreatedDate_is_equal() + { + RestResult.WorkItem.CreatedDate.ShouldEqual(SoapResult.WorkItem.CreatedDate); + } + + [TestMethod] + public void Description_is_equal() + { + RestResult.WorkItem.Description.ShouldEqual(SoapResult.WorkItem.Description); + } + + public override void Given() + { + var credentials = Credentials.CredentialsFactory.CreateCredentials(); + var fac = WorkItemStoreFactory.GetInstance(); + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; + RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + } + + [TestMethod] + public void History_is_equal() + { + RestResult.WorkItem.History.ShouldEqual(SoapResult.WorkItem.History); + } + + [TestMethod] + public void Id_is_equal() + { + RestResult.WorkItem.Id.ShouldEqual(SoapResult.WorkItem.Id); + } + + [TestMethod] + public void IterationPath_is_equal() + { + RestResult.WorkItem.IterationPath.ShouldEqual(SoapResult.WorkItem.IterationPath); + } + + [TestMethod] + public void REST_WorkItem_is_returned() + { + RestResult.WorkItem.ShouldNotBeNull(); + } + + [TestMethod] + public void SOAP_WorkItem_is_returned() + { + SoapResult.WorkItem.ShouldNotBeNull(); + } + + [TestMethod] + public void State_is_equal() + { + RestResult.WorkItem.State.ShouldEqual(SoapResult.WorkItem.State); + } + + [TestMethod] + public void Tags_is_equal() + { + RestResult.WorkItem.Tags.ShouldEqual(SoapResult.WorkItem.Tags); + } + + [TestMethod] + public void Title_is_equal() + { + RestResult.WorkItem.Title.ShouldEqual(SoapResult.WorkItem.Title); + } + + protected class Result : IDisposable + { + public IWorkItem WorkItem { get; set; } + + public IEnumerable Links { get; set; } + + public IWorkItemStore WorkItemStore { get; set; } + + public void Dispose() + { + WorkItemStore?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index abb8b272..0731e368 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -293,6 +293,7 @@ + diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 7d70ef9a..c5ce95d5 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -3,24 +3,41 @@ using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Proxies; +using TfsSoap = Microsoft.Qwiq.Proxies.Soap; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; namespace Microsoft.Qwiq.Core.Tests { - public abstract class WorkItemStoreTests : ContextSpecification + public abstract class WorkItemStoreTests : ContextSpecification + where T : IWorkItemStore { protected IWorkItemStore WorkItemStore; internal MockQueryFactory QueryFactory; + protected abstract T Create(); + public override void Given() { - WorkItemStore = new WorkItemStoreProxy(null, null, QueryFactory); + WorkItemStore = Create(); + } + + public override void Cleanup() + { + WorkItemStore.Dispose(); + } + } + + public abstract class WorkItemStoreSoapTests : WorkItemStoreTests + { + protected override TfsSoap.WorkItemStoreProxy Create() + { + return new TfsSoap.WorkItemStoreProxy(null, null, QueryFactory); } } [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreTests + public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreSoapTests { private IEnumerable _actual; @@ -49,7 +66,7 @@ public void an_empty_result_set_is_returned() } [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreTests + public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreSoapTests { public override void Given() { @@ -76,7 +93,7 @@ public void a_query_string_with_one_id_is_generated() } [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreTests + public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreSoapTests { public override void Given() { @@ -103,7 +120,7 @@ public void a_query_string_with_two_ids_is_generated() } [TestClass] - public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreTests + public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreSoapTests { public override void Given() { diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index eb3f27f3..7cf9ea26 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -368,8 +368,8 @@ - + @@ -390,10 +390,6 @@ - - {1edeb333-3084-42bd-b273-4009b4b18541} - Qwiq.Linq - {d9ed32d7-03fa-468b-ad1a-249cef9c6cdb} Qwiq.Benchmark @@ -402,6 +398,10 @@ {8AC61B6E-BEC1-482D-A043-C65D2D343B35} Qwiq.Core + + {1edeb333-3084-42bd-b273-4009b4b18541} + Qwiq.Linq + {016e8d93-4195-4639-bcd5-77633e8e1681} Qwiq.Mapper diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index d926c3b7..239f76da 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -211,11 +211,11 @@ public string ReproSteps } } - public int Rev + public long Rev { get { - return (int)GetValue("Rev"); + return (long)GetValue("Rev"); } set { @@ -230,9 +230,9 @@ public DateTime RevisedDate set { SetValue("Revised Date", value); } } - public int Revision + public long Revision { - get { return (int)GetValue("Revision"); } + get { return (long)GetValue("Revision"); } set { SetValue("Revision", value); } } From af9c1c85948948d279a52bcf4d391005b8ba761a Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 14 Feb 2017 12:03:11 -0800 Subject: [PATCH 003/251] Update System.IdentityModel.Tokens.Jwt to 4.0.0 --- src/Qwiq.Core/Qwiq.Core.csproj | 5 ++--- src/Qwiq.Core/packages.config | 2 +- src/Qwiq.Identity/Qwiq.Identity.csproj | 5 ++--- src/Qwiq.Identity/packages.config | 2 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 5 ++--- src/Qwiq.Linq/packages.config | 2 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 5 ++--- src/Qwiq.Mapper/packages.config | 2 +- src/Qwiq.Relatives/Qwiq.Relatives.csproj | 5 ++--- src/Qwiq.Relatives/packages.config | 2 +- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 3 +++ test/Qwiq.Benchmark/packages.config | 1 + test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 5 ++--- test/Qwiq.Core.Tests/packages.config | 2 +- .../Qwiq.Identity.Tests.csproj | 5 ++--- test/Qwiq.Identity.Tests/packages.config | 2 +- test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 5 ++--- test/Qwiq.Linq.Tests/packages.config | 2 +- test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj | 15 +++++++-------- test/Qwiq.Mapper.Tests/packages.config | 2 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 5 ++--- test/Qwiq.Mocks/packages.config | 2 +- .../Qwiq.Relatives.Tests.csproj | 5 ++--- test/Qwiq.Relatives.Tests/packages.config | 2 +- 24 files changed, 42 insertions(+), 49 deletions(-) diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 584bba9f..9c4f109e 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -219,9 +219,8 @@ - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index 45afa822..91b1109c 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -13,6 +13,6 @@ - + \ No newline at end of file diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 48b0d390..f2f331b4 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -238,9 +238,8 @@ - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index 601b695e..7d006aa4 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index c5aed866..9b76f782 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -212,9 +212,8 @@ - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index 7bf7f5a6..19e97c6b 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -13,6 +13,6 @@ - + \ No newline at end of file diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 10e0bea5..f5fe7ad3 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -217,9 +217,8 @@ - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index 601b695e..7d006aa4 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/src/Qwiq.Relatives/Qwiq.Relatives.csproj b/src/Qwiq.Relatives/Qwiq.Relatives.csproj index a97ce572..efefe3de 100644 --- a/src/Qwiq.Relatives/Qwiq.Relatives.csproj +++ b/src/Qwiq.Relatives/Qwiq.Relatives.csproj @@ -214,9 +214,8 @@ - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/src/Qwiq.Relatives/packages.config b/src/Qwiq.Relatives/packages.config index 7bf7f5a6..19e97c6b 100644 --- a/src/Qwiq.Relatives/packages.config +++ b/src/Qwiq.Relatives/packages.config @@ -13,6 +13,6 @@ - + \ No newline at end of file diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 27a727c6..a8bd7503 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -121,6 +121,9 @@ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll True + + ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll True diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index 8d8f0693..26c2e5aa 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -42,6 +42,7 @@ + diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index abb8b272..45f55734 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -235,9 +235,8 @@ ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index fcc0df2f..c545a4b9 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -20,7 +20,7 @@ - + diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index 94b6fcca..bc72bca4 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -276,9 +276,8 @@ ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index f40552e5..710bfbdb 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -34,7 +34,7 @@ - + diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index c3dcbe80..2178170c 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -213,9 +213,8 @@ ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index c57ed222..e2257b3f 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -19,7 +19,7 @@ - + diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index eb3f27f3..1fff2cb9 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -277,9 +277,8 @@ ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll @@ -368,8 +367,8 @@ - + @@ -390,10 +389,6 @@ - - {1edeb333-3084-42bd-b273-4009b4b18541} - Qwiq.Linq - {d9ed32d7-03fa-468b-ad1a-249cef9c6cdb} Qwiq.Benchmark @@ -402,6 +397,10 @@ {8AC61B6E-BEC1-482D-A043-C65D2D343B35} Qwiq.Core + + {1edeb333-3084-42bd-b273-4009b4b18541} + Qwiq.Linq + {016e8d93-4195-4639-bcd5-77633e8e1681} Qwiq.Mapper diff --git a/test/Qwiq.Mapper.Tests/packages.config b/test/Qwiq.Mapper.Tests/packages.config index f40552e5..710bfbdb 100644 --- a/test/Qwiq.Mapper.Tests/packages.config +++ b/test/Qwiq.Mapper.Tests/packages.config @@ -34,7 +34,7 @@ - + diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index b303d66d..237ecb92 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -222,9 +222,8 @@ ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/test/Qwiq.Mocks/packages.config b/test/Qwiq.Mocks/packages.config index 3ecf9198..c3b4af6a 100644 --- a/test/Qwiq.Mocks/packages.config +++ b/test/Qwiq.Mocks/packages.config @@ -19,7 +19,7 @@ - + diff --git a/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj b/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj index e2509cce..478a181d 100644 --- a/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj +++ b/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj @@ -228,9 +228,8 @@ ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll - True + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/test/Qwiq.Relatives.Tests/packages.config b/test/Qwiq.Relatives.Tests/packages.config index c57ed222..e2257b3f 100644 --- a/test/Qwiq.Relatives.Tests/packages.config +++ b/test/Qwiq.Relatives.Tests/packages.config @@ -19,7 +19,7 @@ - + From e2cf5e71a1080a9b408669c5315e661c871cd249 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 14 Feb 2017 14:35:51 -0800 Subject: [PATCH 004/251] Update Packages Set minimum version required for dependent packages. --- src/Qwiq.Core/Qwiq.Core.csproj | 33 ++--- src/Qwiq.Core/app.config | 65 ---------- src/Qwiq.Core/packages.config | 11 +- src/Qwiq.Identity/App.config | 71 ---------- src/Qwiq.Identity/Qwiq.Identity.csproj | 38 ++---- src/Qwiq.Identity/packages.config | 12 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 38 ++---- src/Qwiq.Linq/app.config | 65 ---------- src/Qwiq.Linq/packages.config | 12 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 38 ++---- src/Qwiq.Mapper/app.config | 73 ----------- src/Qwiq.Mapper/packages.config | 12 +- src/Qwiq.Relatives/Qwiq.Relatives.csproj | 38 ++---- src/Qwiq.Relatives/app.config | 57 -------- src/Qwiq.Relatives/packages.config | 12 +- test/Qwiq.Benchmark/app.config | 6 +- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 45 ++----- test/Qwiq.Core.Tests/app.config | 90 +------------ test/Qwiq.Core.Tests/packages.config | 14 +- .../Qwiq.Identity.Tests.csproj | 122 +++--------------- test/Qwiq.Identity.Tests/app.config | 76 +---------- test/Qwiq.Identity.Tests/packages.config | 16 +-- test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 41 ++---- test/Qwiq.Linq.Tests/app.config | 94 +------------- test/Qwiq.Linq.Tests/packages.config | 15 +-- .../Qwiq.Mapper.Tests.csproj | 41 ++---- test/Qwiq.Mapper.Tests/app.config | 108 +--------------- test/Qwiq.Mapper.Tests/packages.config | 13 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 45 ++----- test/Qwiq.Mocks/app.config | 89 +------------ test/Qwiq.Mocks/packages.config | 18 +-- .../Qwiq.Relatives.Tests.csproj | 41 ++---- test/Qwiq.Relatives.Tests/app.config | 86 +----------- test/Qwiq.Relatives.Tests/packages.config | 13 +- 34 files changed, 214 insertions(+), 1334 deletions(-) delete mode 100644 src/Qwiq.Core/app.config delete mode 100644 src/Qwiq.Identity/App.config delete mode 100644 src/Qwiq.Linq/app.config delete mode 100644 src/Qwiq.Mapper/app.config delete mode 100644 src/Qwiq.Relatives/app.config diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 9c4f109e..97b478f3 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -39,25 +39,14 @@ True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -211,8 +200,8 @@ ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -223,9 +212,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll @@ -321,7 +309,6 @@ - Designer diff --git a/src/Qwiq.Core/app.config b/src/Qwiq.Core/app.config deleted file mode 100644 index b97cb2a4..00000000 --- a/src/Qwiq.Core/app.config +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index 91b1109c..985d2373 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -2,17 +2,16 @@ - - - - + + + - + - + \ No newline at end of file diff --git a/src/Qwiq.Identity/App.config b/src/Qwiq.Identity/App.config deleted file mode 100644 index 4f3aa9db..00000000 --- a/src/Qwiq.Identity/App.config +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index f2f331b4..ac6ed2c1 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -51,7 +51,6 @@ - @@ -60,25 +59,14 @@ ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -232,8 +220,8 @@ ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -242,15 +230,13 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index 7d006aa4..8e44fa6e 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -2,18 +2,16 @@ - - - - - + + + - + - + \ No newline at end of file diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index 9b76f782..0995837e 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -34,25 +34,14 @@ 4 - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -206,8 +195,8 @@ ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -216,15 +205,13 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll @@ -270,7 +257,6 @@ - diff --git a/src/Qwiq.Linq/app.config b/src/Qwiq.Linq/app.config deleted file mode 100644 index 418f021a..00000000 --- a/src/Qwiq.Linq/app.config +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index 19e97c6b..55cf6a09 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -1,18 +1,16 @@  - - - - - + + + - + - + \ No newline at end of file diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index f5fe7ad3..e3cd7a46 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -38,25 +38,14 @@ ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -210,8 +199,8 @@ ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -221,15 +210,13 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll @@ -256,7 +243,6 @@ - diff --git a/src/Qwiq.Mapper/app.config b/src/Qwiq.Mapper/app.config deleted file mode 100644 index 107db2a0..00000000 --- a/src/Qwiq.Mapper/app.config +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index 7d006aa4..8e44fa6e 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -2,18 +2,16 @@ - - - - - + + + - + - + \ No newline at end of file diff --git a/src/Qwiq.Relatives/Qwiq.Relatives.csproj b/src/Qwiq.Relatives/Qwiq.Relatives.csproj index efefe3de..d3505c86 100644 --- a/src/Qwiq.Relatives/Qwiq.Relatives.csproj +++ b/src/Qwiq.Relatives/Qwiq.Relatives.csproj @@ -34,25 +34,14 @@ - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -206,8 +195,8 @@ ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -218,16 +207,14 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll @@ -246,7 +233,6 @@ - diff --git a/src/Qwiq.Relatives/app.config b/src/Qwiq.Relatives/app.config deleted file mode 100644 index dc01fb92..00000000 --- a/src/Qwiq.Relatives/app.config +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Qwiq.Relatives/packages.config b/src/Qwiq.Relatives/packages.config index 19e97c6b..55cf6a09 100644 --- a/src/Qwiq.Relatives/packages.config +++ b/src/Qwiq.Relatives/packages.config @@ -1,18 +1,16 @@  - - - - - + + + - + - + \ No newline at end of file diff --git a/test/Qwiq.Benchmark/app.config b/test/Qwiq.Benchmark/app.config index e5b02790..bb2ca40d 100644 --- a/test/Qwiq.Benchmark/app.config +++ b/test/Qwiq.Benchmark/app.config @@ -8,7 +8,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 45f55734..a6a1dc52 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -45,25 +45,14 @@ ..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -213,16 +202,12 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll True - - ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -231,17 +216,12 @@ - - ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll @@ -265,9 +245,8 @@ True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Core.Tests/app.config b/test/Qwiq.Core.Tests/app.config index 09d23e3c..aae9f36b 100644 --- a/test/Qwiq.Core.Tests/app.config +++ b/test/Qwiq.Core.Tests/app.config @@ -2,93 +2,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -113,9 +33,7 @@ - - + - - + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index c545a4b9..c0c4565b 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -2,23 +2,19 @@ - - - - - + + + - - + - @@ -43,5 +39,5 @@ - + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index bc72bca4..2ab2fe36 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -40,135 +40,96 @@ ..\..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll - True ..\..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll - True ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.9\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll - True ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll - True ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll - True ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - True ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Client.dll - True ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Common.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Diff.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll @@ -180,175 +141,130 @@ ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - True ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - True ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - True ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Client.dll - True ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Common.dll - True ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - True - - - ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True ..\..\packages\Should.1.1.20\lib\Should.dll - True ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll - True ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll - True - - - ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - True ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll - True ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll - True ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - True - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll - True ..\..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll - True ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll - True ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - True ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll - True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll - True ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll - True ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll - True @@ -370,11 +286,6 @@ - - - - - {d9ed32d7-03fa-468b-ad1a-249cef9c6cdb} @@ -401,6 +312,11 @@ Upda + + + + + @@ -429,15 +345,15 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + - - \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/app.config b/test/Qwiq.Identity.Tests/app.config index 051ae97e..d8a3e7bd 100644 --- a/test/Qwiq.Identity.Tests/app.config +++ b/test/Qwiq.Identity.Tests/app.config @@ -3,80 +3,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -101,9 +41,7 @@ - - + - - + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index 710bfbdb..7f615e2e 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -5,22 +5,19 @@ - - + + - - - + - - + @@ -28,7 +25,6 @@ - @@ -40,7 +36,6 @@ - @@ -52,7 +47,6 @@ - @@ -72,5 +66,5 @@ - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index 2178170c..4f535030 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -39,25 +39,14 @@ 4 - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -191,16 +180,12 @@ ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll True - - ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -217,9 +202,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll @@ -243,9 +227,8 @@ True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Linq.Tests/app.config b/test/Qwiq.Linq.Tests/app.config index 8d32b4fc..9b678d31 100644 --- a/test/Qwiq.Linq.Tests/app.config +++ b/test/Qwiq.Linq.Tests/app.config @@ -2,100 +2,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index e2257b3f..f6fd32d6 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -1,18 +1,13 @@  - - - - - + + + - - - - + @@ -41,6 +36,4 @@ - - \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 1fff2cb9..054877d2 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -67,25 +67,14 @@ ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -235,16 +224,12 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll True - - ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -290,9 +275,8 @@ - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll @@ -333,9 +317,8 @@ ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Mapper.Tests/app.config b/test/Qwiq.Mapper.Tests/app.config index 25927603..86855163 100644 --- a/test/Qwiq.Mapper.Tests/app.config +++ b/test/Qwiq.Mapper.Tests/app.config @@ -3,36 +3,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -42,80 +14,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/packages.config b/test/Qwiq.Mapper.Tests/packages.config index 710bfbdb..3fcbf768 100644 --- a/test/Qwiq.Mapper.Tests/packages.config +++ b/test/Qwiq.Mapper.Tests/packages.config @@ -5,22 +5,19 @@ - - + + - - - + - - + @@ -72,5 +69,5 @@ - + \ No newline at end of file diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 237ecb92..5c042758 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -33,25 +33,14 @@ - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -201,16 +190,12 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll True - - ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -218,17 +203,12 @@ - - ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll @@ -252,9 +232,8 @@ True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Mocks/app.config b/test/Qwiq.Mocks/app.config index 8a35d931..9b678d31 100644 --- a/test/Qwiq.Mocks/app.config +++ b/test/Qwiq.Mocks/app.config @@ -1,96 +1,11 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + \ No newline at end of file diff --git a/test/Qwiq.Mocks/packages.config b/test/Qwiq.Mocks/packages.config index c3b4af6a..26988f96 100644 --- a/test/Qwiq.Mocks/packages.config +++ b/test/Qwiq.Mocks/packages.config @@ -1,35 +1,28 @@  - - - - - + + + - - + - - - - @@ -41,6 +34,5 @@ - - + \ No newline at end of file diff --git a/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj b/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj index 478a181d..c889e360 100644 --- a/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj +++ b/test/Qwiq.Relatives.Tests/Qwiq.Relatives.Tests.csproj @@ -38,25 +38,14 @@ 4 - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.10.305231913\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll - - ..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - True + + ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -206,16 +195,12 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll True - - ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -232,9 +217,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll @@ -258,9 +242,8 @@ True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Relatives.Tests/app.config b/test/Qwiq.Relatives.Tests/app.config index d6e1d98e..9b678d31 100644 --- a/test/Qwiq.Relatives.Tests/app.config +++ b/test/Qwiq.Relatives.Tests/app.config @@ -2,92 +2,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/test/Qwiq.Relatives.Tests/packages.config b/test/Qwiq.Relatives.Tests/packages.config index e2257b3f..8cb07c3a 100644 --- a/test/Qwiq.Relatives.Tests/packages.config +++ b/test/Qwiq.Relatives.Tests/packages.config @@ -1,18 +1,15 @@  - - - - - + + + - - + @@ -42,5 +39,5 @@ - + \ No newline at end of file From 6e578ac522ad4643ee8a7e2d33d7df904585d392 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 6 Mar 2017 16:23:50 -0800 Subject: [PATCH 005/251] Set default client type to SOAP --- src/Qwiq.Core/WorkItemStoreFactory.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Qwiq.Core/WorkItemStoreFactory.cs b/src/Qwiq.Core/WorkItemStoreFactory.cs index 89002307..b247d37d 100644 --- a/src/Qwiq.Core/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core/WorkItemStoreFactory.cs @@ -22,11 +22,11 @@ public interface IWorkItemStoreFactory IWorkItemStore Create(Uri endpoint, IEnumerable credentials, ClientType type = ClientType.Default); } - public enum ClientType + public enum ClientType : short { - Default, - Soap, - Rest + Default = 0, + Soap = 0, + Rest = 1 } public class WorkItemStoreFactory : IWorkItemStoreFactory @@ -60,16 +60,17 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia var tfs = ExceptionHandlingDynamicProxyFactory.Create(new TfsTeamProjectCollectionProxy(tfsNative)); - IWorkItemStore wis = null; + IWorkItemStore wis; switch (type) { case ClientType.Rest: wis = CreateRestWorkItemStore(tfs); break; case ClientType.Soap: - case ClientType.Default: wis = CreateSoapWorkItemStore(tfs); break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); } return ExceptionHandlingDynamicProxyFactory.Create(wis); @@ -86,7 +87,7 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia private static IWorkItemStore CreateRestWorkItemStore(IInternalTfsTeamProjectCollection tfs) { - var workItemStore = tfs.GetClient(); + var workItemStore = tfs.GetClient(); return new TfsRest.WorkItemStoreProxy(tfs, workItemStore); } From 0989986d91e7e20769c1be74db87dce4933f103d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 6 Mar 2017 16:24:24 -0800 Subject: [PATCH 006/251] Add OAuth token support to CredentialsFactory --- .../Credentials/CredentialsFactory.cs | 93 ++++++++++++++++--- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/src/Qwiq.Core/Credentials/CredentialsFactory.cs b/src/Qwiq.Core/Credentials/CredentialsFactory.cs index 98b242ab..d212d9b7 100644 --- a/src/Qwiq.Core/Credentials/CredentialsFactory.cs +++ b/src/Qwiq.Core/Credentials/CredentialsFactory.cs @@ -1,35 +1,100 @@ using System.Collections.Generic; +using System.Linq; using System.Net; + +using Microsoft.IdentityModel.Clients.ActiveDirectory; using Microsoft.TeamFoundation.Client; +using WindowsCredential = Microsoft.TeamFoundation.Client.WindowsCredential; + namespace Microsoft.Qwiq.Credentials { public static class CredentialsFactory { - public static IEnumerable CreateCredentials(string username = null, string password = null) + /// + /// Depending on the specific setup of the TFS server, it may or may not accept credentials of specific type. To accommodate for that + /// without making the configuration more complicated (by making the user explicitly set which type of credentials to use), we just + /// provide a list of all the relevant credential types we support in order of priority, and when connecting to TFS, we can try them + /// in order and just go with the first one that succeeds. + /// + public static IEnumerable CreateCredentials(string username = null, string password = null, string accessToken = null) { - if (!string.IsNullOrEmpty(password)) + return CreateCredentialsImpl(username, password, accessToken).Select(c => new TfsCredentials(c)); + } + + private static IEnumerable CreateCredentialsImpl( + string username = null, + string password = null, + string accessToken = null) + { + // First try OAuth, as this is our preferred method + foreach (var c in GetOAuthCredentials(accessToken)) { - if (!string.IsNullOrEmpty(username)) - { - yield return new TfsCredentials(new TfsClientCredentials(new AadCredential(username, password)) { AllowInteractive = false }); + yield return c; + } - // Service Identity - Non Interactive - yield return new TfsCredentials(new TfsClientCredentials(new SimpleWebTokenCredential(username, password)) {AllowInteractive = false}); + // Next try Username/Password combinations + foreach (var c in GetServiceIdentityCredentials(username, password)) + { + yield return c; + } - // Try username and password. If user sent a PAT with a username, will work like a regular password - yield return new TfsCredentials(new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential(username, password))) { AllowInteractive = false }); - } + // Next try PAT + foreach (var c in GetServiceIdentityPatCredentials(password)) + { + yield return c; + } - // PAT - user can specify a PAT with no username - yield return new TfsCredentials(new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential("", password))) { AllowInteractive = false }); + // Next try basic credentials + foreach (var c in GetBasicCredentials(username, password)) + { + yield return c; } // User did not specify a username or a password, so use the process identity - yield return new TfsCredentials(new TfsClientCredentials(new WindowsCredential(false)) { AllowInteractive = false }); + yield return new TfsClientCredentials(new WindowsCredential(false)) { AllowInteractive = false }; // Use the Windows identity of the logged on user - yield return new TfsCredentials(new TfsClientCredentials(true)); + yield return new TfsClientCredentials(true); + } + + private static IEnumerable GetBasicCredentials( + string username = null, + string password = null) + { + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) + { + yield return new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential(username, password))) { AllowInteractive = false }; + } + } + + private static IEnumerable GetServiceIdentityCredentials( + string username = null, + string password = null) + { + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) + { + yield return new TfsClientCredentials(new AadCredential(username, password)) { AllowInteractive = false }; + yield return new TfsClientCredentials(new WindowsCredential(new NetworkCredential(username, password))) { AllowInteractive = false }; + } + } + + private static IEnumerable GetServiceIdentityPatCredentials(string password = null) + { + if (!string.IsNullOrEmpty(password)) + { + yield return new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential(string.Empty, password))) { AllowInteractive = false }; + } + } + + private static IEnumerable GetOAuthCredentials(string accessToken = null) + { + if (!string.IsNullOrEmpty(accessToken)) + { + yield return new TfsClientCredentials(new OAuthTokenCredential(accessToken)); + } + + // TODO: Request token directly } } } From e52dac70b76cf84afc875e3c49ec6293d779866b Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 6 Mar 2017 16:25:01 -0800 Subject: [PATCH 007/251] Remove unused usings from WorkItemLinkInfoProxy --- src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs index 21f8e4a0..f7ccc5fd 100644 --- a/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkInfoProxy.cs @@ -1,9 +1,5 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Qwiq.Proxies.Rest { @@ -13,7 +9,7 @@ public class WorkItemLinkInfoProxy : IWorkItemLinkInfo internal WorkItemLinkInfoProxy(WorkItemLink item) { - _item = item; + _item = item; } public bool IsLocked => throw new NotImplementedException(); From 59c324e232eb51c179da666cf2ce6642591921db Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 6 Mar 2017 16:37:53 -0800 Subject: [PATCH 008/251] Add OAuth token support to CredentialsFactory --- .../Credentials/CredentialsFactory.cs | 88 ++++++++++++++++--- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/Qwiq.Core/Credentials/CredentialsFactory.cs b/src/Qwiq.Core/Credentials/CredentialsFactory.cs index 98b242ab..316ed3f4 100644 --- a/src/Qwiq.Core/Credentials/CredentialsFactory.cs +++ b/src/Qwiq.Core/Credentials/CredentialsFactory.cs @@ -1,35 +1,95 @@ using System.Collections.Generic; +using System.Linq; using System.Net; + using Microsoft.TeamFoundation.Client; +using WindowsCredential = Microsoft.TeamFoundation.Client.WindowsCredential; + namespace Microsoft.Qwiq.Credentials { public static class CredentialsFactory { - public static IEnumerable CreateCredentials(string username = null, string password = null) + /// + /// Depending on the specific setup of the TFS server, it may or may not accept credentials of specific type. To accommodate for that + /// without making the configuration more complicated (by making the user explicitly set which type of credentials to use), we just + /// provide a list of all the relevant credential types we support in order of priority, and when connecting to TFS, we can try them + /// in order and just go with the first one that succeeds. + /// + public static IEnumerable CreateCredentials(string username = null, string password = null, string accessToken = null) { - if (!string.IsNullOrEmpty(password)) + return CreateCredentialsImpl(username, password, accessToken).Select(c => new TfsCredentials(c)); + } + + private static IEnumerable CreateCredentialsImpl( + string username = null, + string password = null, + string accessToken = null) + { + // First try OAuth, as this is our preferred method + foreach (var c in GetOAuthCredentials(accessToken)) { - if (!string.IsNullOrEmpty(username)) - { - yield return new TfsCredentials(new TfsClientCredentials(new AadCredential(username, password)) { AllowInteractive = false }); + yield return c; + } - // Service Identity - Non Interactive - yield return new TfsCredentials(new TfsClientCredentials(new SimpleWebTokenCredential(username, password)) {AllowInteractive = false}); + // Next try Username/Password combinations + foreach (var c in GetServiceIdentityCredentials(username, password)) + { + yield return c; + } - // Try username and password. If user sent a PAT with a username, will work like a regular password - yield return new TfsCredentials(new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential(username, password))) { AllowInteractive = false }); - } + // Next try PAT + foreach (var c in GetServiceIdentityPatCredentials(password)) + { + yield return c; + } - // PAT - user can specify a PAT with no username - yield return new TfsCredentials(new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential("", password))) { AllowInteractive = false }); + // Next try basic credentials + foreach (var c in GetBasicCredentials(username, password)) + { + yield return c; } // User did not specify a username or a password, so use the process identity - yield return new TfsCredentials(new TfsClientCredentials(new WindowsCredential(false)) { AllowInteractive = false }); + yield return new TfsClientCredentials(new WindowsCredential(false)) { AllowInteractive = false }; // Use the Windows identity of the logged on user - yield return new TfsCredentials(new TfsClientCredentials(true)); + yield return new TfsClientCredentials(true); + } + + private static IEnumerable GetBasicCredentials( + string username = null, + string password = null) + { + if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) yield break; + + yield return new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential(username, password))) { AllowInteractive = false }; + } + + private static IEnumerable GetServiceIdentityCredentials( + string username = null, + string password = null) + { + if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) yield break; + + yield return new TfsClientCredentials(new AadCredential(username, password)) { AllowInteractive = false }; + yield return new TfsClientCredentials(new WindowsCredential(new NetworkCredential(username, password))) { AllowInteractive = false }; + } + + private static IEnumerable GetServiceIdentityPatCredentials(string password = null) + { + if (string.IsNullOrEmpty(password)) yield break; + + yield return new TfsClientCredentials(new BasicAuthCredential(new NetworkCredential(string.Empty, password))) { AllowInteractive = false }; + } + + private static IEnumerable GetOAuthCredentials(string accessToken = null) + { + if (string.IsNullOrEmpty(accessToken)) yield break; + + yield return new TfsClientCredentials(new OAuthTokenCredential(accessToken)); + + // TODO: Request token directly } } } From 2d924b7ecad6a502a4b1f887394ec66186477093 Mon Sep 17 00:00:00 2001 From: Peter Lavallee Date: Tue, 7 Mar 2017 11:21:51 -0800 Subject: [PATCH 009/251] Expose ApplyRules on IWorkItem --- src/Qwiq.Core/IWorkItem.cs | 12 ++++++++++++ src/Qwiq.Core/Proxies/WorkItemProxy.cs | 5 +++++ test/Qwiq.Mocks/MockWorkItem.cs | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 59fcc9ea..7284f3c6 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -212,5 +212,17 @@ public interface IWorkItem /// An ArrayList of the fields in this work item that are not valid. /// IEnumerable Validate(); + + /// + /// Applies the server rules for validation and fix up to the work item. + /// + /// + /// If true, will set ChangedBy to the user context of the . + /// If false, ChangedBy will not be modified. + /// + /// + /// Use ApplyRules(true) in the case where you want "transparent fix ups". + /// + void ApplyRules(bool doNotUpdateChangedBy = false); } } diff --git a/src/Qwiq.Core/Proxies/WorkItemProxy.cs b/src/Qwiq.Core/Proxies/WorkItemProxy.cs index 4941bde0..61e85c7d 100644 --- a/src/Qwiq.Core/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemProxy.cs @@ -416,6 +416,11 @@ public void Save(SaveFlags saveFlags) throw new ServerRejectedChangesException(ex); } } + + public void ApplyRules(bool doNotUpdateChangedBy = false) + { + _item.ApplyRules(doNotUpdateChangedBy); + } } } diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index d926c3b7..49bd6c7c 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -398,5 +398,9 @@ private void SetValue(string field, object value) _properties.Add(field, new MockField(value, value) { Name = field }); } } + + public void ApplyRules(bool doNotUpdateChangedBy = false) + { + } } } From 0cccd795bdf1b24f6e7541316879d20431598e55 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 15 Mar 2017 12:36:43 -0700 Subject: [PATCH 010/251] Add support for null substitution Extend the `FieldDefinitionAttribute` to allow for a value to be substituted when the source field is empty. For example, if a non-compulsory field "Priority" is Int32, and the target property type is also Int32, a default value of 0 is placed on the target. This permits users to create properties with one of two types: Nullable or Int32 with the substitute. Resolves #112 +semver: minor --- .../Attributes/AttributeMapperStrategy.cs | 56 +++++++++++++--- .../Attributes/FieldDefinitionAttribute.cs | 24 +++++-- src/Qwiq.Mapper/TypeParser.cs | 64 +++++++++---------- test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 54 ++++++++++++++++ 4 files changed, 151 insertions(+), 47 deletions(-) diff --git a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs index 30a70ea0..fc0b35a3 100644 --- a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs @@ -84,41 +84,76 @@ private void MapImpl(Type targetWorkItemType, IWorkItem sourceWorkItem, TypeAcce #if DEBUG Trace.TraceInformation("{0}: Mapping {1}", GetType().Name, sourceWorkItem.Id); #endif + var properties = PropertiesOnWorkItemCache( + _inspector, + sourceWorkItem, + targetWorkItemType, + typeof(FieldDefinitionAttribute)); - foreach (var property in PropertiesOnWorkItemCache(_inspector, sourceWorkItem, targetWorkItemType, typeof(FieldDefinitionAttribute))) + foreach (var property in properties) { var a = PropertyInfoFieldCache(_inspector, property); if (a == null) continue; var fieldName = a.FieldName; var convert = a.RequireConversion; + var nullSub = a.NullSubstitute; + var fieldValue = sourceWorkItem[fieldName]; try { if (convert) { - var value = _typeParser.Parse(property.PropertyType, sourceWorkItem[fieldName]); - accessor[targetWorkItem, property.Name] = value; + try + { + fieldValue = _typeParser.Parse(property.PropertyType, fieldValue, nullSub); + } + catch (Exception) + { + try + { + Trace.TraceWarning( + "Could not convert value of field '{0}' ({1}) to type {2}", + fieldName, + fieldValue.GetType().Name, + property.PropertyType.Name); + } + catch (Exception) + { + // Best effort + } + } } - else + + if (fieldValue == null && nullSub != null) { - accessor[targetWorkItem, property.Name] = sourceWorkItem[fieldName]; + fieldValue = nullSub; } + + accessor[targetWorkItem, property.Name] = fieldValue; + } - catch (Exception e) + catch(NullReferenceException) when (fieldValue == null) { + // This is most likely the cause of the field being null and the target property type not accepting nulls + // For example: mapping null to an int instead of int? + try { Trace.TraceWarning( - "Could not convert value of field '{0}' ({1}) to type {2}", + "Could not map field '{0}' from type '{1}' to type '{2}'. Target '{2}.{3}' does not accept null values.", fieldName, - sourceWorkItem[fieldName].GetType().Name, - property.PropertyType.Name); + sourceWorkItem.Type.Name, + targetWorkItemType.Name, + $"{property.Name} ({property.PropertyType.FullName})"); } catch (Exception) { + // Best effort } - + } + catch (Exception e) + { try { Trace.TraceWarning( @@ -130,6 +165,7 @@ private void MapImpl(Type targetWorkItemType, IWorkItem sourceWorkItem, TypeAcce } catch (Exception) { + // Best effort } } } diff --git a/src/Qwiq.Mapper/Attributes/FieldDefinitionAttribute.cs b/src/Qwiq.Mapper/Attributes/FieldDefinitionAttribute.cs index ce062532..e7f7e4c4 100644 --- a/src/Qwiq.Mapper/Attributes/FieldDefinitionAttribute.cs +++ b/src/Qwiq.Mapper/Attributes/FieldDefinitionAttribute.cs @@ -5,21 +5,37 @@ namespace Microsoft.Qwiq.Mapper.Attributes [AttributeUsage(AttributeTargets.Property)] public class FieldDefinitionAttribute : Attribute { + /// Value for cannot be null, empty, or only whitespace. public FieldDefinitionAttribute(string name) - : this(name, false) + : this(name, false, null) { - } + /// Value for cannot be null, empty, or only whitespace. public FieldDefinitionAttribute(string name, bool requireConversion) + : this(name, requireConversion, null) { + } + + /// Value for cannot be null, empty, or only whitespace. + public FieldDefinitionAttribute(string name, object nullSubstitute) + : this(name, false, nullSubstitute) + { + } + + /// Value for cannot be null, empty, or only whitespace. + public FieldDefinitionAttribute(string name, bool requireConversion, object nullSubstitute) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); FieldName = name; RequireConversion = requireConversion; + NullSubstitute = nullSubstitute; } public string FieldName { get; } + public object NullSubstitute { get; } + public bool RequireConversion { get; } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Mapper/TypeParser.cs b/src/Qwiq.Mapper/TypeParser.cs index 59eef0e9..4b27bacd 100644 --- a/src/Qwiq.Mapper/TypeParser.cs +++ b/src/Qwiq.Mapper/TypeParser.cs @@ -10,7 +10,36 @@ public class TypeParser : ITypeParser { public object Parse(Type destinationType, object value, object defaultValue) { - return ParseImpl(destinationType, value, new Lazy(() => defaultValue)); + return ParseImpl( + destinationType, + value, + new Lazy(() => defaultValue ?? GetDefaultValueOfType(destinationType))); + } + + public object Parse(Type destinationType, object input) + { + return ParseImpl(destinationType, input, new Lazy(() => GetDefaultValueOfType(destinationType))); + } + + public T Parse(object value) + { + return Parse(value, default(T)); + } + + public T Parse(object value, T defaultValue) + { + return (T)Parse(typeof(T), value, defaultValue); + } + + private static object GetDefaultValueOfType(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + private static bool IsGenericNullable(Type type) + { + return type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } private static object ParseImpl(Type destinationType, object value, Lazy defaultValueFactory) @@ -32,7 +61,6 @@ private static object ParseImpl(Type destinationType, object value, Lazy return value; } - object result; if (TryConvert(destinationType, value, out result)) @@ -53,36 +81,6 @@ private static object ParseImpl(Type destinationType, object value, Lazy return null; } - public object Parse(Type destinationType, object input) - { - return ParseImpl(destinationType, input, new Lazy(()=> GetDefaultValueOfType(destinationType))); - } - - public T Parse(object value) - { - return Parse(value, default(T)); - } - - public T Parse(object value, T defaultValue) - { - return (T)Parse(typeof(T), value, defaultValue); - } - - - - private static object GetDefaultValueOfType(Type type) - { - return type.IsValueType - ? Activator.CreateInstance(type) - : null; - } - - private static bool IsGenericNullable(Type type) - { - return type.IsGenericType && - type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); - } - private static bool TryConvert(Type destinationType, object value, out object result) { var valueType = value.GetType(); @@ -124,4 +122,4 @@ private static bool ValueRepresentsNull(object value) return value == null || value == DBNull.Value; } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 254cce29..e4f17c83 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -81,6 +81,60 @@ public override void Cleanup() } } + [TestClass] + public class when_an_issue_is_mapped_with_a_field_containing_null_that_does_not_accept_null_and_has_a_null_substitute : WorkItemMapperContext + { + public override void Given() + { + SourceWorkItems = new[] { new MockWorkItem("MissingFields", WorkItemBackingStore) }; + WorkItemStore = new MockWorkItemStore(SourceWorkItems); + base.Given(); + } + + [TestMethod] + public void the_mapped_property_is_the_substituted_value() + { + Actual.DoesNotExist.ShouldEqual(-1); + } + + [WorkItemType("MissingFields")] + public class MockModelWithMissingField : IIdentifiable + { + [FieldDefinition("Id")] + public virtual int Id { get; internal set; } + + [FieldDefinition("NullableField", -1)] + public virtual int DoesNotExist { get; internal set; } + } + } + + [TestClass] + public class when_an_issue_is_mapped_with_a_field_containing_null_that_does_not_accept_null_and_convert_and_has_a_null_substitute : WorkItemMapperContext + { + public override void Given() + { + SourceWorkItems = new[] { new MockWorkItem("Default", WorkItemBackingStore) }; + WorkItemStore = new MockWorkItemStore(SourceWorkItems); + base.Given(); + } + + [TestMethod] + public void the_mapped_property_is_the_substituted_value() + { + Actual.DoesNotExist.ShouldEqual(-1); + } + + [WorkItemType("Default")] + public class MockModelWithMissingField : IIdentifiable + { + [FieldDefinition("Id")] + public int Id { get; internal set; } + + [FieldDefinition("NullableField", true, -1)] + public int DoesNotExist { get; internal set; } + } + } + [TestClass] // ReSharper disable once InconsistentNaming public class when_the_issue_factory_parses_an_issue_with_links : WorkItemMapperContext From e7e0f3bcf02dec265379a7c1f5da9fe3125b38a4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 15 Mar 2017 12:51:56 -0700 Subject: [PATCH 011/251] Remove invalid FieldDefinition case --- test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs index d2572b98..f2059597 100644 --- a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs +++ b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs @@ -35,10 +35,6 @@ public string AnIdentity [FieldDefinition("NotAnIdentityField")] public string NotAnIdentity { get; set; } - [IdentityField] - [FieldDefinition("")] - public string Empty { get; set; } - [IdentityField] [FieldDefinition(NonExistantField)] public string NonExistant { get; set; } From 71d2db66238cbb2395ddf8a221fccf90decdc516 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 15 Mar 2017 15:38:37 -0700 Subject: [PATCH 012/251] Expose identity properties on IWorkItemStore Resolves #115 --- src/Qwiq.Core/IWorkItemStore.cs | 27 ++++-- src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs | 74 ++++++++------ src/Qwiq.Core/Qwiq.Core.csproj | 4 +- .../Mocks/InstrumentedMockWorkItemStore.cs | 96 +++++++++++-------- test/Qwiq.Mocks/MockWorkItemStore.cs | 37 ++++--- 5 files changed, 145 insertions(+), 93 deletions(-) diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 7219a3e6..3dd9c985 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; namespace Microsoft.Qwiq @@ -10,14 +9,26 @@ namespace Microsoft.Qwiq /// public interface IWorkItemStore : IDisposable { + IEnumerable Projects { get; } + + ITfsTeamProjectCollection TeamProjectCollection { get; } + + TimeZone TimeZone { get; } + + string UserDisplayName { get; } + + string UserIdentityName { get; } + + string UserSid { get; } + + IEnumerable WorkItemLinkTypes { get; } + IEnumerable Query(string wiql, bool dayPrecision = false); - IEnumerable QueryLinks(string wiql, bool dayPrecision = false); + IEnumerable Query(IEnumerable ids, DateTime? asOf = null); + IWorkItem Query(int id, DateTime? asOf = null); - ITfsTeamProjectCollection TeamProjectCollection { get; } - IEnumerable Projects { get; } - IEnumerable WorkItemLinkTypes { get; } - TimeZone TimeZone { get; } - } -} + IEnumerable QueryLinks(string wiql, bool dayPrecision = false); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs index 90f77969..06ae48a9 100644 --- a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + using Microsoft.Qwiq.Exceptions; + using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Proxies @@ -14,18 +16,23 @@ namespace Microsoft.Qwiq.Proxies public class WorkItemStoreProxy : IWorkItemStore { private readonly IQueryFactory _queryFactory; + private readonly IInternalTfsTeamProjectCollection _tfs; + private readonly TfsWorkItem.WorkItemStore _workItemStore; - internal WorkItemStoreProxy(IInternalTfsTeamProjectCollection tfs, TfsWorkItem.WorkItemStore workItemStore, IQueryFactory queryFactory) + internal WorkItemStoreProxy( + IInternalTfsTeamProjectCollection tfs, + TfsWorkItem.WorkItemStore workItemStore, + IQueryFactory queryFactory) { _tfs = tfs; _workItemStore = workItemStore; _queryFactory = queryFactory; - } #region IDisposable + public void Dispose() { Dispose(true); @@ -39,23 +46,40 @@ protected virtual void Dispose(bool disposing) _tfs.Dispose(); } } - #endregion - public ITfsTeamProjectCollection TeamProjectCollection + #endregion IDisposable + + public IEnumerable Projects { - get { return _tfs; } + get + { + return + _workItemStore.Projects.Cast() + .Select( + item => + ExceptionHandlingDynamicProxyFactory.Create(new ProjectProxy(item))); + } } - public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) + public ITfsTeamProjectCollection TeamProjectCollection { - try + get { - var query = _queryFactory.Create(wiql, dayPrecision); - return query.RunLinkQuery(); + return _tfs; } - catch (TfsWorkItem.ValidationException ex) + } + + public TimeZone TimeZone => _workItemStore.TimeZone; + + public IEnumerable WorkItemLinkTypes + { + get { - throw new ValidationException(ex); + return + _workItemStore.WorkItemLinkTypes.Select( + item => + ExceptionHandlingDynamicProxyFactory.Create( + new WorkItemLinkTypeProxy(item))); } } @@ -95,29 +119,23 @@ public IWorkItem Query(int id, DateTime? asOf = null) return Query(new[] { id }, asOf).SingleOrDefault(); } - public IEnumerable Projects + public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) { - get + try { - return - _workItemStore.Projects.Cast() - .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new ProjectProxy(item))); + var query = _queryFactory.Create(wiql, dayPrecision); + return query.RunLinkQuery(); } - } - - public IEnumerable WorkItemLinkTypes - { - get + catch (TfsWorkItem.ValidationException ex) { - return - _workItemStore.WorkItemLinkTypes - .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeProxy(item))); + throw new ValidationException(ex); } } - public TimeZone TimeZone => _workItemStore.TimeZone; - } - + public string UserDisplayName => _workItemStore.UserDisplayName; -} + public string UserIdentityName => _workItemStore.UserIdentityName; + public string UserSid => _workItemStore.UserSid; + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index a4f7594e..0ace0b11 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -243,10 +243,10 @@ + - @@ -277,10 +277,10 @@ + - diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 636d4a04..57008e5e 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq.Mapper.Tests.Mocks { - class InstrumentedMockWorkItemStore : IWorkItemStore + internal class InstrumentedMockWorkItemStore : IWorkItemStore { private readonly IWorkItemStore _innerWorkItemStore; @@ -12,43 +12,32 @@ public InstrumentedMockWorkItemStore(IWorkItemStore innerWorkItemStore) _innerWorkItemStore = innerWorkItemStore; } - public void Dispose() + public IEnumerable Projects { - _innerWorkItemStore.Dispose(); + get + { + ProjectsCallCount += 1; + return _innerWorkItemStore.Projects; + } } - public IEnumerable Query(string wiql, bool dayPrecision = false) - { - QueryStringCallCount += 1; - return _innerWorkItemStore.Query(wiql, dayPrecision); - } - public int QueryStringCallCount { get; private set; } + public int ProjectsCallCount { get; private set; } - public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) + public int QueryCallCount { - QueryLinksCallCount += 1; - return _innerWorkItemStore.QueryLinks(wiql, dayPrecision); + get + { + return QueryIdCallCount + QueryIdsCallCount + QueryLinksCallCount + QueryStringCallCount; + } } - public int QueryLinksCallCount { get; private set; } - public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) - { - QueryIdsCallCount += 1; - return _innerWorkItemStore.Query(ids, asOf); - } + public int QueryIdCallCount { get; private set; } + public int QueryIdsCallCount { get; private set; } - public IWorkItem Query(int id, DateTime? asOf = null) - { - QueryIdCallCount += 1; - return _innerWorkItemStore.Query(id, asOf); - } - public int QueryIdCallCount { get; private set; } + public int QueryLinksCallCount { get; private set; } - public int QueryCallCount - { - get { return QueryIdCallCount + QueryIdsCallCount + QueryLinksCallCount + QueryStringCallCount; } - } + public int QueryStringCallCount { get; private set; } public ITfsTeamProjectCollection TeamProjectCollection { @@ -58,17 +47,16 @@ public ITfsTeamProjectCollection TeamProjectCollection return _innerWorkItemStore.TeamProjectCollection; } } + public int TeamProjectCollectionCallCount { get; private set; } - public IEnumerable Projects - { - get - { - ProjectsCallCount += 1; - return _innerWorkItemStore.Projects; - } - } - public int ProjectsCallCount { get; private set; } + public TimeZone TimeZone => _innerWorkItemStore.TimeZone; + + public string UserDisplayName => _innerWorkItemStore.UserDisplayName; + + public string UserIdentityName => _innerWorkItemStore.UserIdentityName; + + public string UserSid => _innerWorkItemStore.UserSid; public IEnumerable WorkItemLinkTypes { @@ -79,9 +67,35 @@ public IEnumerable WorkItemLinkTypes } } - public TimeZone TimeZone => _innerWorkItemStore.TimeZone; - public int WorkItemLinkTypesCallCount { get; private set; } - } -} + public void Dispose() + { + _innerWorkItemStore.Dispose(); + } + + public IEnumerable Query(string wiql, bool dayPrecision = false) + { + QueryStringCallCount += 1; + return _innerWorkItemStore.Query(wiql, dayPrecision); + } + + public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + { + QueryIdsCallCount += 1; + return _innerWorkItemStore.Query(ids, asOf); + } + + public IWorkItem Query(int id, DateTime? asOf = null) + { + QueryIdCallCount += 1; + return _innerWorkItemStore.Query(id, asOf); + } + + public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) + { + QueryLinksCallCount += 1; + return _innerWorkItemStore.QueryLinks(wiql, dayPrecision); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 284b869d..18791d48 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -10,13 +10,13 @@ namespace Microsoft.Qwiq.Mocks { public class MockWorkItemStore : IWorkItemStore { - private readonly IEnumerable _links; + private static readonly Random Instance = new Random(); - private readonly IList _workItems; + private readonly IEnumerable _links; private readonly IDictionary _lookup; - private static readonly Random Instance = new Random(); + private readonly IList _workItems; public MockWorkItemStore() : this(new MockTfsTeamProjectCollection(), new MockProject()) @@ -47,8 +47,8 @@ public MockWorkItemStore(IEnumerable workItems) _lookup = _workItems.ToDictionary(k => k.Id, e => e); } - public MockWorkItemStore(IEnumerable workItems, IEnumerable links ) - :this(workItems) + public MockWorkItemStore(IEnumerable workItems, IEnumerable links) + : this(workItems) { _links = links; } @@ -62,13 +62,21 @@ public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnume public IEnumerable Projects { get; set; } - public ITfsTeamProjectCollection TeamProjectCollection { get; set; } + public bool SimulateQueryTimes { get; set; } - public IEnumerable WorkItemLinkTypes { get { return CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s)); } } + public ITfsTeamProjectCollection TeamProjectCollection { get; set; } public TimeZone TimeZone { get; } - public bool SimulateQueryTimes { get; set; } + public IEnumerable WorkItemLinkTypes + { + get + { + return CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s)); + } + } + + private int WaitTime => Instance.Next(0, 3000); public void Dispose() { @@ -107,10 +115,7 @@ public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) Thread.Sleep(sleep); } - return _lookup - .Where(p => h.Contains(p.Key)) - .Select(p => p.Value) - .ToList(); + return _lookup.Where(p => h.Contains(p.Key)).Select(p => p.Value).ToList(); } public IWorkItem Query(int id, DateTime? asOf = null) @@ -137,6 +142,10 @@ protected void Dispose(bool disposing) } } - private int WaitTime => Instance.Next(0, 3000); + public string UserDisplayName => string.Empty; + + public string UserIdentityName => string.Empty; + + public string UserSid => string.Empty; } -} +} \ No newline at end of file From 767e038970c6e76c97a4128a0572cd6c3320c579 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 15 Mar 2017 16:23:09 -0700 Subject: [PATCH 013/251] Make `TfsCredentials.ctor` public --- src/Qwiq.Core/Credentials/TfsCredentials.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Qwiq.Core/Credentials/TfsCredentials.cs b/src/Qwiq.Core/Credentials/TfsCredentials.cs index 4a3d1ba5..e0cd8da7 100644 --- a/src/Qwiq.Core/Credentials/TfsCredentials.cs +++ b/src/Qwiq.Core/Credentials/TfsCredentials.cs @@ -1,15 +1,17 @@ +using System.Diagnostics; + using Microsoft.TeamFoundation.Client; namespace Microsoft.Qwiq.Credentials { + [DebuggerStepThrough] public sealed class TfsCredentials { - internal TfsCredentials(TfsClientCredentials credentials) + public TfsCredentials(TfsClientCredentials credentials) { Credentials = credentials; } internal TfsClientCredentials Credentials { get; private set; } } -} - +} \ No newline at end of file From 19282e9259a0b6164fd2c465e9a7ce1a1d03c740 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 16 Mar 2017 14:25:18 -0700 Subject: [PATCH 014/251] Expose AuthorizedCredentials and AuthorizedUser Fixes #115 --- .../Credentials/CredentialsFactory.cs | 30 +++++-- src/Qwiq.Core/ITfsTeamProjectCollection.cs | 14 +++- src/Qwiq.Core/IWorkItemStore.cs | 4 + .../Proxies/TeamFoundationIdentityProxy.cs | 72 ++++++++--------- .../Proxies/TfsTeamProjectCollectionProxy.cs | 52 ++++++++----- src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs | 75 +++++++++++------- src/Qwiq.Core/WorkItemStoreFactory.cs | 35 +++++---- src/Qwiq.Linq/Qwiq.Linq.csproj | 2 +- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 21 ----- test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 78 ++++++++++--------- test/Qwiq.Core.Tests/packages.config | 26 ------- .../Mocks/InstrumentedMockWorkItemStore.cs | 4 + .../MockTfsTeamProjectCollection.cs | 13 ++-- test/Qwiq.Mocks/MockWorkItemStore.cs | 15 ++-- 14 files changed, 234 insertions(+), 207 deletions(-) diff --git a/src/Qwiq.Core/Credentials/CredentialsFactory.cs b/src/Qwiq.Core/Credentials/CredentialsFactory.cs index 316ed3f4..98cf602d 100644 --- a/src/Qwiq.Core/Credentials/CredentialsFactory.cs +++ b/src/Qwiq.Core/Credentials/CredentialsFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -18,34 +19,47 @@ public static class CredentialsFactory /// public static IEnumerable CreateCredentials(string username = null, string password = null, string accessToken = null) { - return CreateCredentialsImpl(username, password, accessToken).Select(c => new TfsCredentials(c)); + return CreateCredentials( + new Lazy(() => username), + new Lazy(() => password), + new Lazy(() => accessToken)); + } + + public static IEnumerable CreateCredentials(Lazy username, Lazy password, Lazy accessToken) + { + if (username == null) throw new ArgumentNullException(nameof(username)); + if (password == null) throw new ArgumentNullException(nameof(password)); + if (accessToken == null) throw new ArgumentNullException(nameof(accessToken)); + + return CreateCredentialsImpl(username, password, accessToken) + .Select(c => new TfsCredentials(c)); } private static IEnumerable CreateCredentialsImpl( - string username = null, - string password = null, - string accessToken = null) + Lazy username, + Lazy password, + Lazy accessToken) { // First try OAuth, as this is our preferred method - foreach (var c in GetOAuthCredentials(accessToken)) + foreach (var c in GetOAuthCredentials(accessToken.Value)) { yield return c; } // Next try Username/Password combinations - foreach (var c in GetServiceIdentityCredentials(username, password)) + foreach (var c in GetServiceIdentityCredentials(username.Value, password.Value)) { yield return c; } // Next try PAT - foreach (var c in GetServiceIdentityPatCredentials(password)) + foreach (var c in GetServiceIdentityPatCredentials(password.Value)) { yield return c; } // Next try basic credentials - foreach (var c in GetBasicCredentials(username, password)) + foreach (var c in GetBasicCredentials(username.Value, password.Value)) { yield return c; } diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index 57dba2a6..05b8b073 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -1,16 +1,24 @@ using System; +using Microsoft.Qwiq.Credentials; + namespace Microsoft.Qwiq { public interface ITfsTeamProjectCollection { - IIdentityManagementService IdentityManagementService { get; } + TfsCredentials AuthorizedCredentials { get; } + + ITeamFoundationIdentity AuthorizedIdentity { get; } + ICommonStructureService CommonStructureService { get; } + + bool HasAuthenticated { get; } + + IIdentityManagementService IdentityManagementService { get; } } internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection, IDisposable { T GetService(); } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 3dd9c985..844cf3d5 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using Microsoft.Qwiq.Credentials; + namespace Microsoft.Qwiq { /// @@ -9,6 +11,8 @@ namespace Microsoft.Qwiq /// public interface IWorkItemStore : IDisposable { + TfsCredentials AuthorizedCredentials { get; } + IEnumerable Projects { get; } ITfsTeamProjectCollection TeamProjectCollection { get; } diff --git a/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs b/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs index 2caf910a..7b0fb678 100644 --- a/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs +++ b/src/Qwiq.Core/Proxies/TeamFoundationIdentityProxy.cs @@ -10,54 +10,54 @@ public class TeamFoundationIdentityProxy : ITeamFoundationIdentity { private readonly Tfs.TeamFoundationIdentity _identity; + private readonly Lazy _descriptor; + + private readonly Lazy> _memberOf; + + private readonly Lazy> _members; + internal TeamFoundationIdentityProxy(Tfs.TeamFoundationIdentity identity) { _identity = identity; - } + _descriptor = + new Lazy( + () => + ExceptionHandlingDynamicProxyFactory.Create( + new IdentityDescriptorProxy(_identity?.Descriptor))); - public IIdentityDescriptor Descriptor - { - get { return ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptorProxy(_identity.Descriptor)); } - } + _memberOf = + new Lazy>( + () => + _identity?.MemberOf.Select( + item => + ExceptionHandlingDynamicProxyFactory.Create( + new IdentityDescriptorProxy(item))) ?? Enumerable.Empty()); - public string DisplayName - { - get { return _identity.DisplayName; } + _members = + new Lazy>( + () => + _identity?.Members.Select( + item => + ExceptionHandlingDynamicProxyFactory.Create( + new IdentityDescriptorProxy(item))) ?? Enumerable.Empty()); } - public bool IsActive - { - get { return _identity.IsActive; } - } + public IIdentityDescriptor Descriptor => _descriptor.Value; - public bool IsContainer - { - get { return _identity.IsContainer; } - } + public string DisplayName => _identity?.DisplayName; - public IEnumerable MemberOf - { - get { return _identity.MemberOf.Select(item => ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptorProxy(item))); } - } + public bool IsActive => _identity?.IsActive ?? false; - public IEnumerable Members - { - get { return _identity.Members.Select(item => ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptorProxy(item))); } - } + public bool IsContainer => _identity?.IsContainer ?? false; - public Guid TeamFoundationId - { - get { return _identity.TeamFoundationId; } - } + public IEnumerable MemberOf => _memberOf.Value; - public string UniqueName - { - get { return _identity.UniqueName; } - } + public IEnumerable Members => _members.Value; - public int UniqueUserId - { - get { return _identity.UniqueUserId; } - } + public Guid TeamFoundationId => _identity?.TeamFoundationId ?? Guid.Empty; + + public string UniqueName => _identity?.UniqueName; + + public int UniqueUserId => _identity?.UniqueUserId ?? -1; } } diff --git a/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs b/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs index 0ad893c6..0d6232b4 100644 --- a/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/TfsTeamProjectCollectionProxy.cs @@ -1,49 +1,63 @@ using System; + +using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation; namespace Microsoft.Qwiq.Proxies { public class TfsTeamProjectCollectionProxy : IInternalTfsTeamProjectCollection { + private readonly Lazy _css; + + private readonly Lazy _ims; + private readonly Tfs.Client.TfsTeamProjectCollection _tfs; internal TfsTeamProjectCollectionProxy(Tfs.Client.TfsTeamProjectCollection tfs) { _tfs = tfs; + _css = + new Lazy( + () => + ExceptionHandlingDynamicProxyFactory.Create( + new CommonStructureServiceProxy(_tfs?.GetService()))); + _ims = + new Lazy( + () => + ExceptionHandlingDynamicProxyFactory.Create( + new IdentityManagementServiceProxy( + _tfs?.GetService()))); } - public IIdentityManagementService IdentityManagementService - { - get - { - return ExceptionHandlingDynamicProxyFactory.Create(new IdentityManagementServiceProxy(_tfs.GetService())); - } - } + public TfsCredentials AuthorizedCredentials => new TfsCredentials(_tfs?.ClientCredentials); + + public ITeamFoundationIdentity AuthorizedIdentity => new TeamFoundationIdentityProxy(_tfs?.AuthorizedIdentity); + + public ICommonStructureService CommonStructureService => _css.Value; + + public bool HasAuthenticated => _tfs?.HasAuthenticated ?? false; - public ICommonStructureService CommonStructureService + public IIdentityManagementService IdentityManagementService => _ims.Value; + + public void Dispose() { - get { return ExceptionHandlingDynamicProxyFactory.Create(new CommonStructureServiceProxy(_tfs.GetService())); } + Dispose(true); + GC.SuppressFinalize(this); } public T GetService() { - return _tfs.GetService(); + return _tfs == null ? default(T) : _tfs.GetService(); } protected virtual void Dispose(bool disposing) { if (disposing) { - _tfs.Dispose(); + _tfs?.Dispose(); } } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs index 06ae48a9..586ed0a4 100644 --- a/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemStoreProxy.cs @@ -3,7 +3,9 @@ using System.Globalization; using System.Linq; +using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; +using Microsoft.TeamFoundation.Client; using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -15,22 +17,45 @@ namespace Microsoft.Qwiq.Proxies /// public class WorkItemStoreProxy : IWorkItemStore { - private readonly IQueryFactory _queryFactory; + private readonly Lazy _queryFactory; - private readonly IInternalTfsTeamProjectCollection _tfs; + private readonly Lazy _tfs; - private readonly TfsWorkItem.WorkItemStore _workItemStore; + private readonly Lazy _workItemStore; internal WorkItemStoreProxy( - IInternalTfsTeamProjectCollection tfs, - TfsWorkItem.WorkItemStore workItemStore, - IQueryFactory queryFactory) + TfsTeamProjectCollection tfsNative, + Func queryFactory) + : this( + () => + ExceptionHandlingDynamicProxyFactory.Create( + new TfsTeamProjectCollectionProxy(tfsNative)), + queryFactory) { - _tfs = tfs; - _workItemStore = workItemStore; - _queryFactory = queryFactory; } + internal WorkItemStoreProxy( + Func tpcFactory, + Func wisFactory, + Func queryFactory) + { + if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); + if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); + if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); + _tfs = new Lazy(() => tpcFactory()); + _workItemStore = new Lazy(() => wisFactory()); + _queryFactory = new Lazy(() => queryFactory.Invoke(_workItemStore?.Value)); + } + + internal WorkItemStoreProxy( + Func tpcFactory, + Func queryFactory) + : this(tpcFactory, () => tpcFactory()?.GetService(), queryFactory) + { + } + + public TfsCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; + #region IDisposable public void Dispose() @@ -43,7 +68,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _tfs.Dispose(); + if (_tfs.IsValueCreated) _tfs.Value?.Dispose(); } } @@ -54,29 +79,29 @@ public IEnumerable Projects get { return - _workItemStore.Projects.Cast() + _workItemStore.Value.Projects.Cast() .Select( item => ExceptionHandlingDynamicProxyFactory.Create(new ProjectProxy(item))); } } - public ITfsTeamProjectCollection TeamProjectCollection - { - get - { - return _tfs; - } - } + public ITfsTeamProjectCollection TeamProjectCollection => _tfs.Value; + + public TimeZone TimeZone => _workItemStore.Value.TimeZone; - public TimeZone TimeZone => _workItemStore.TimeZone; + public string UserDisplayName => _workItemStore.Value.UserDisplayName; + + public string UserIdentityName => _workItemStore.Value.UserIdentityName; + + public string UserSid => _workItemStore.Value.UserSid; public IEnumerable WorkItemLinkTypes { get { return - _workItemStore.WorkItemLinkTypes.Select( + _workItemStore.Value.WorkItemLinkTypes.Select( item => ExceptionHandlingDynamicProxyFactory.Create( new WorkItemLinkTypeProxy(item))); @@ -87,7 +112,7 @@ public IEnumerable Query(string wiql, bool dayPrecision = false) { try { - var query = _queryFactory.Create(wiql, dayPrecision); + var query = _queryFactory.Value.Create(wiql, dayPrecision); return query.RunQuery(); } catch (TfsWorkItem.ValidationException ex) @@ -123,7 +148,7 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision { try { - var query = _queryFactory.Create(wiql, dayPrecision); + var query = _queryFactory.Value.Create(wiql, dayPrecision); return query.RunLinkQuery(); } catch (TfsWorkItem.ValidationException ex) @@ -131,11 +156,5 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision throw new ValidationException(ex); } } - - public string UserDisplayName => _workItemStore.UserDisplayName; - - public string UserIdentityName => _workItemStore.UserIdentityName; - - public string UserSid => _workItemStore.UserSid; } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemStoreFactory.cs b/src/Qwiq.Core/WorkItemStoreFactory.cs index 8ed74a22..536b7a18 100644 --- a/src/Qwiq.Core/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core/WorkItemStoreFactory.cs @@ -7,7 +7,6 @@ using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Build.WebApi; using Microsoft.TeamFoundation.Client; -using Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq { @@ -17,10 +16,11 @@ public interface IWorkItemStoreFactory IWorkItemStore Create(Uri endpoint, IEnumerable credentials); } - + public class WorkItemStoreFactory : IWorkItemStoreFactory { - private static readonly Lazy Instance = new Lazy(() => new WorkItemStoreFactory()); + private static readonly Lazy Instance = + new Lazy(() => new WorkItemStoreFactory()); private WorkItemStoreFactory() { @@ -33,29 +33,33 @@ public static IWorkItemStoreFactory GetInstance() public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) { - return Create(endpoint, new [] { credentials }); + return Create(endpoint, new[] { credentials }); } public IWorkItemStore Create(Uri endpoint, IEnumerable credentials) { - Func queryFactoryFunc = QueryFactory.GetInstance; foreach (var credential in credentials) { try { var tfsNative = ConnectToTfsCollection(endpoint, credential.Credentials); - System.Diagnostics.Trace.TraceInformation("TFS connection attempt success with {0}/{1}.", credential.Credentials.Windows.GetType(), credential.Credentials.Federated.GetType()); - - var tfs = ExceptionHandlingDynamicProxyFactory.Create(new TfsTeamProjectCollectionProxy(tfsNative)); - var workItemStore = tfs.GetService(); - var queryFactory = queryFactoryFunc.Invoke(workItemStore); + System.Diagnostics.Trace.TraceInformation( + "TFS connection attempt success with {0}/{1}.", + credential.Credentials.Windows.GetType(), + credential.Credentials.Federated.GetType()); - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemStoreProxy(tfs, workItemStore, queryFactory)); + return + ExceptionHandlingDynamicProxyFactory.Create( + new WorkItemStoreProxy(tfsNative, QueryFactory.GetInstance)); } catch (TeamFoundationServerUnauthorizedException e) { - System.Diagnostics.Trace.TraceWarning("TFS connection attempt failed with {0}/{1}.\n Exception: {2}", credential.Credentials.Windows.GetType(), credential.Credentials.Federated.GetType(), e); + System.Diagnostics.Trace.TraceWarning( + "TFS connection attempt failed with {0}/{1}.\n Exception: {2}", + credential.Credentials.Windows.GetType(), + credential.Credentials.Federated.GetType(), + e); } } @@ -63,9 +67,7 @@ public IWorkItemStore Create(Uri endpoint, IEnumerable credentia throw new AccessDeniedException("Invalid credentials"); } - private static TfsTeamProjectCollection ConnectToTfsCollection( - Uri endpoint, - TfsClientCredentials credentials) + private static TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, TfsClientCredentials credentials) { var tfsServer = new TfsTeamProjectCollection(endpoint, credentials); tfsServer.EnsureAuthenticated(); @@ -78,5 +80,4 @@ private static TfsTeamProjectCollection ConnectToTfsCollection( return tfsServer; } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index d4dcd74d..fc35a4de 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -244,7 +244,6 @@ - @@ -253,6 +252,7 @@ + diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index a6a1dc52..baee819e 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -215,7 +215,6 @@ True - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll @@ -224,26 +223,6 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll - True - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 7d70ef9a..58466ecc 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -1,28 +1,31 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Proxies; +using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Should; namespace Microsoft.Qwiq.Core.Tests { - public abstract class WorkItemStoreTests : ContextSpecification + [TestClass] + public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreTests { - protected IWorkItemStore WorkItemStore; - internal MockQueryFactory QueryFactory; - - public override void Given() + [TestMethod] + public void a_query_is_created() { - WorkItemStore = new WorkItemStoreProxy(null, null, QueryFactory); + QueryFactory.CreateCallCount.ShouldEqual(1); } - } - [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreTests - { - private IEnumerable _actual; + [TestMethod] + public void a_query_string_with_two_ids_and_an_asof_clause_is_generated() + { + QueryFactory.QueryWiqls.Single() + .ShouldEqual("SELECT * FROM WorkItems WHERE [System.Id] IN (1, 2) ASOF '2012-01-12 12:23:34Z'"); + } public override void Given() { @@ -32,8 +35,14 @@ public override void Given() public override void When() { - _actual = WorkItemStore.Query(new int[] {}); + WorkItemStore.Query(new[] { 1, 2 }, new DateTime(2012, 1, 12, 12, 23, 34, DateTimeKind.Utc)); } + } + + [TestClass] + public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreTests + { + private IEnumerable _actual; [TestMethod] public void a_query_is_never_created() @@ -46,11 +55,7 @@ public void an_empty_result_set_is_returned() { _actual.ShouldBeEmpty(); } - } - [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreTests - { public override void Given() { QueryFactory = new MockQueryFactory(); @@ -59,9 +64,13 @@ public override void Given() public override void When() { - WorkItemStore.Query(new[] { 1 }); + _actual = WorkItemStore.Query(new int[] { }); } + } + [TestClass] + public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreTests + { [TestMethod] public void a_query_is_created() { @@ -73,11 +82,7 @@ public void a_query_string_with_one_id_is_generated() { QueryFactory.QueryWiqls.Single().ShouldEqual("SELECT * FROM WorkItems WHERE [System.Id] IN (1)"); } - } - [TestClass] - public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreTests - { public override void Given() { QueryFactory = new MockQueryFactory(); @@ -86,9 +91,13 @@ public override void Given() public override void When() { - WorkItemStore.Query(new[] { 1, 2 }); + WorkItemStore.Query(new[] { 1 }); } + } + [TestClass] + public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreTests + { [TestMethod] public void a_query_is_created() { @@ -100,11 +109,7 @@ public void a_query_string_with_two_ids_is_generated() { QueryFactory.QueryWiqls.Single().ShouldEqual("SELECT * FROM WorkItems WHERE [System.Id] IN (1, 2)"); } - } - [TestClass] - public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreTests - { public override void Given() { QueryFactory = new MockQueryFactory(); @@ -113,20 +118,19 @@ public override void Given() public override void When() { - WorkItemStore.Query(new[] { 1, 2 }, new DateTime(2012, 1, 12, 12, 23, 34, DateTimeKind.Utc)); + WorkItemStore.Query(new[] { 1, 2 }); } + } - [TestMethod] - public void a_query_is_created() - { - QueryFactory.CreateCallCount.ShouldEqual(1); - } + public abstract class WorkItemStoreTests : ContextSpecification + { + internal MockQueryFactory QueryFactory; - [TestMethod] - public void a_query_string_with_two_ids_and_an_asof_clause_is_generated() + protected IWorkItemStore WorkItemStore; + + public override void Given() { - QueryFactory.QueryWiqls.Single().ShouldEqual("SELECT * FROM WorkItems WHERE [System.Id] IN (1, 2) ASOF '2012-01-12 12:23:34Z'"); + WorkItemStore = new WorkItemStoreProxy((TfsTeamProjectCollection)null, s => QueryFactory); } } -} - +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index c0c4565b..56d8858c 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -12,32 +12,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 57008e5e..7f5a411e 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using Microsoft.Qwiq.Credentials; + namespace Microsoft.Qwiq.Mapper.Tests.Mocks { internal class InstrumentedMockWorkItemStore : IWorkItemStore @@ -12,6 +14,8 @@ public InstrumentedMockWorkItemStore(IWorkItemStore innerWorkItemStore) _innerWorkItemStore = innerWorkItemStore; } + public TfsCredentials AuthorizedCredentials => _innerWorkItemStore.AuthorizedCredentials; + public IEnumerable Projects { get diff --git a/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs b/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs index 9046bdf2..5bdd0f43 100644 --- a/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs +++ b/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; +using Microsoft.Qwiq.Credentials; namespace Microsoft.Qwiq.Mocks { @@ -17,8 +14,14 @@ public MockTfsTeamProjectCollection(IIdentityManagementService identityManagemen IdentityManagementService = identityManagementService; } + public TfsCredentials AuthorizedCredentials { get; set; } + + public ITeamFoundationIdentity AuthorizedIdentity { get; set; } + public ICommonStructureService CommonStructureService { get; set; } + public bool HasAuthenticated { get; set; } + public IIdentityManagementService IdentityManagementService { get; set; } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 18791d48..ae3d13c2 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; +using Microsoft.Qwiq.Credentials; using Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Mocks @@ -60,6 +61,8 @@ public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnume _lookup = _workItems.ToDictionary(k => k.Id, e => e); } + public TfsCredentials AuthorizedCredentials => null; + public IEnumerable Projects { get; set; } public bool SimulateQueryTimes { get; set; } @@ -68,6 +71,12 @@ public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnume public TimeZone TimeZone { get; } + public string UserDisplayName => TeamProjectCollection.AuthorizedIdentity.DisplayName; + + public string UserIdentityName => TeamProjectCollection.AuthorizedIdentity.DisplayName; + + public string UserSid => TeamProjectCollection.AuthorizedIdentity.Descriptor.Identifier; + public IEnumerable WorkItemLinkTypes { get @@ -141,11 +150,5 @@ protected void Dispose(bool disposing) { } } - - public string UserDisplayName => string.Empty; - - public string UserIdentityName => string.Empty; - - public string UserSid => string.Empty; } } \ No newline at end of file From 066d505fea735bceeea3289ad6264a93da73700f Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 16 Mar 2017 14:36:38 -0700 Subject: [PATCH 015/251] Make all Lazy args optional on CredentialsFactory --- src/Qwiq.Core/Credentials/CredentialsFactory.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Core/Credentials/CredentialsFactory.cs b/src/Qwiq.Core/Credentials/CredentialsFactory.cs index 98cf602d..b31e386c 100644 --- a/src/Qwiq.Core/Credentials/CredentialsFactory.cs +++ b/src/Qwiq.Core/Credentials/CredentialsFactory.cs @@ -25,11 +25,14 @@ public static IEnumerable CreateCredentials(string username = nu new Lazy(() => accessToken)); } - public static IEnumerable CreateCredentials(Lazy username, Lazy password, Lazy accessToken) + public static IEnumerable CreateCredentials( + Lazy username = null, + Lazy password = null, + Lazy accessToken = null) { - if (username == null) throw new ArgumentNullException(nameof(username)); - if (password == null) throw new ArgumentNullException(nameof(password)); - if (accessToken == null) throw new ArgumentNullException(nameof(accessToken)); + if (username == null) username = new Lazy(() => string.Empty); + if (password == null) password = new Lazy(() => string.Empty); + if (accessToken == null) accessToken = new Lazy(() => string.Empty); return CreateCredentialsImpl(username, password, accessToken) .Select(c => new TfsCredentials(c)); From cfa8013f51d6e6a352279eb46f9759225af345c4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 22 Mar 2017 08:24:43 -0700 Subject: [PATCH 016/251] Update GitVersionTask to stable 3.6.5 --- src/Qwiq.Core/Qwiq.Core.csproj | 5 +++-- src/Qwiq.Core/app.config | 19 +++++++++++++++++++ src/Qwiq.Core/packages.config | 2 +- src/Qwiq.Identity/Qwiq.Identity.csproj | 5 +++-- src/Qwiq.Identity/app.config | 11 +++++++++++ src/Qwiq.Identity/packages.config | 2 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 5 +++-- src/Qwiq.Linq/app.config | 11 +++++++++++ src/Qwiq.Linq/packages.config | 2 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 5 +++-- src/Qwiq.Mapper/app.config | 11 +++++++++++ src/Qwiq.Mapper/packages.config | 2 +- src/Qwiq.Relatives/Qwiq.Relatives.csproj | 5 +++-- src/Qwiq.Relatives/app.config | 11 +++++++++++ src/Qwiq.Relatives/packages.config | 2 +- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 4 ++-- test/Qwiq.Benchmark/packages.config | 2 +- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 4 ++-- test/Qwiq.Core.Tests/packages.config | 2 +- .../Qwiq.Identity.Tests.csproj | 4 ++-- test/Qwiq.Identity.Tests/packages.config | 2 +- test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 4 ++-- test/Qwiq.Linq.Tests/packages.config | 2 +- .../Qwiq.Mapper.Tests.csproj | 4 ++-- test/Qwiq.Mapper.Tests/packages.config | 2 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 4 ++-- test/Qwiq.Mocks/packages.config | 2 +- .../Qwiq.Relatives.Tests.csproj | 4 ++-- test/Qwiq.Relatives.Tests/packages.config | 2 +- 29 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 src/Qwiq.Core/app.config create mode 100644 src/Qwiq.Identity/app.config create mode 100644 src/Qwiq.Linq/app.config create mode 100644 src/Qwiq.Mapper/app.config create mode 100644 src/Qwiq.Relatives/app.config diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 0ace0b11..c9a12bb4 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -312,6 +312,7 @@ + Designer @@ -327,9 +328,9 @@ - + - + \ No newline at end of file diff --git a/src/Qwiq.Core/app.config b/src/Qwiq.Core/app.config new file mode 100644 index 00000000..2909d3ec --- /dev/null +++ b/src/Qwiq.Core/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index 985d2373..aa5a54b2 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index ac6ed2c1..73ae31e6 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -51,6 +51,7 @@ + @@ -257,9 +258,9 @@ - + - + \ No newline at end of file diff --git a/src/Qwiq.Identity/app.config b/src/Qwiq.Identity/app.config new file mode 100644 index 00000000..9b678d31 --- /dev/null +++ b/src/Qwiq.Identity/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index 8e44fa6e..34f8a349 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index fc35a4de..cdb7f048 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -258,6 +258,7 @@ + @@ -274,9 +275,9 @@ - + - + \ No newline at end of file diff --git a/src/Qwiq.Linq/app.config b/src/Qwiq.Linq/app.config new file mode 100644 index 00000000..9b678d31 --- /dev/null +++ b/src/Qwiq.Linq/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index 55cf6a09..cd838551 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index e3cd7a46..c94141f6 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -243,6 +243,7 @@ + @@ -263,9 +264,9 @@ - + - + \ No newline at end of file diff --git a/src/Qwiq.Mapper/app.config b/src/Qwiq.Mapper/app.config new file mode 100644 index 00000000..9b678d31 --- /dev/null +++ b/src/Qwiq.Mapper/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index 8e44fa6e..34f8a349 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/Qwiq.Relatives/Qwiq.Relatives.csproj b/src/Qwiq.Relatives/Qwiq.Relatives.csproj index d3505c86..2bd9a983 100644 --- a/src/Qwiq.Relatives/Qwiq.Relatives.csproj +++ b/src/Qwiq.Relatives/Qwiq.Relatives.csproj @@ -233,6 +233,7 @@ + @@ -257,9 +258,9 @@ - + - + \ No newline at end of file diff --git a/src/Qwiq.Relatives/app.config b/src/Qwiq.Relatives/app.config new file mode 100644 index 00000000..9b678d31 --- /dev/null +++ b/src/Qwiq.Relatives/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Relatives/packages.config b/src/Qwiq.Relatives/packages.config index 55cf6a09..cd838551 100644 --- a/src/Qwiq.Relatives/packages.config +++ b/src/Qwiq.Relatives/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index a8bd7503..56845f40 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -209,8 +209,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index 26c2e5aa..9f76f49c 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -4,7 +4,7 @@ - + diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index baee819e..0bed5bad 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -297,8 +297,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index 56d8858c..f936c777 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -1,7 +1,7 @@  - + diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index 2ab2fe36..41d3592e 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -345,15 +345,15 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index 7f615e2e..bcd40f23 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -4,7 +4,7 @@ - + diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index 4f535030..96d20c3c 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -297,8 +297,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index f6fd32d6..3b9b61d7 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 054877d2..1e410471 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -430,12 +430,12 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index f936c777..7b915861 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -4,14 +4,16 @@ - - - - - + + + + + + + - + - - + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index 41d3592e..f8482ebe 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -59,134 +59,139 @@ ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - - ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll + + ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Common.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - - ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Common.dll + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Diff.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Git.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll + + ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Client.dll + + ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll - - ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Common.dll + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll - - ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll @@ -208,8 +213,8 @@ ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll @@ -246,6 +251,10 @@ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll + + ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll @@ -314,7 +323,6 @@ - @@ -350,10 +358,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/app.config b/test/Qwiq.Identity.Tests/app.config deleted file mode 100644 index d8a3e7bd..00000000 --- a/test/Qwiq.Identity.Tests/app.config +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index bcd40f23..738a5c16 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -11,13 +11,15 @@ - - - - - + + + + + + + - + @@ -30,7 +32,7 @@ - + @@ -66,5 +68,5 @@ - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index 96d20c3c..4256f392 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -39,14 +39,14 @@ 4 - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - - ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll + + ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll @@ -56,21 +56,24 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Common.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Client.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - True + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll @@ -84,6 +87,9 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll True + + ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Git.Client.dll True @@ -104,9 +110,8 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll @@ -116,13 +121,11 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll @@ -136,9 +139,8 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll @@ -152,9 +154,8 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll @@ -176,30 +177,31 @@ ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll - True ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll @@ -208,25 +210,24 @@ ..\..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll - True ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True + + ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll @@ -250,7 +251,6 @@ - @@ -296,9 +296,9 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/app.config b/test/Qwiq.Linq.Tests/app.config deleted file mode 100644 index 9b678d31..00000000 --- a/test/Qwiq.Linq.Tests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index 3b9b61d7..7a39bf75 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -3,18 +3,20 @@ - - - + + + + + - + - + @@ -36,4 +38,5 @@ + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 1e410471..568fe711 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -67,170 +67,140 @@ ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll True - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.22.302111727\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - - ..\..\packages\WindowsAzure.ServiceBus.2.5.1.0\lib\net40-full\Microsoft.ServiceBus.dll + + ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - - ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Common.dll - True + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Diff.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll True - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Client.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Common.dll - True + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - ..\..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - True + + ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll @@ -262,8 +232,8 @@ ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll True - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll @@ -309,6 +279,10 @@ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll True + + ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll True @@ -368,7 +342,6 @@ - @@ -430,12 +403,12 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config new file mode 100644 index 00000000..1c296451 --- /dev/null +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/QueryTestsBase.cs b/test/Qwiq.Mapper.Tests/QueryTestsBase.cs index 73ce3ff3..7ca85de0 100644 --- a/test/Qwiq.Mapper.Tests/QueryTestsBase.cs +++ b/test/Qwiq.Mapper.Tests/QueryTestsBase.cs @@ -1,9 +1,10 @@ using System.Linq; -using Microsoft.Qwiq.Core.Tests; + using Microsoft.Qwiq.Linq; using Microsoft.Qwiq.Linq.Visitors; using Microsoft.Qwiq.Mapper.Attributes; using Microsoft.Qwiq.Mocks; +using Microsoft.Qwiq.Tests.Common; namespace Microsoft.Qwiq.Mapper.Tests { diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 568fe711..dc9dc065 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -39,275 +39,12 @@ 4 - - ..\..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll - True - - - ..\..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll - True - - - ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.9\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll - True - - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - True - - - ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - - ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - ..\..\packages\Should.1.1.20\lib\Should.dll True - - ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll - True - - - ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - - ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll - True - - - ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True - - - ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - True - - - ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll - True - - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - - ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll - True - - - ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - True - - - - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - - - ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - - ..\..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll - True - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True - - - ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True - - - - ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll - True - - - ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - - - ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - True - - - ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll - True - - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll - - - - ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll - True - - - ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll - True - - - ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll - True - @@ -332,7 +69,6 @@ - @@ -341,14 +77,10 @@ - + - - {d9ed32d7-03fa-468b-ad1a-249cef9c6cdb} - Qwiq.Benchmark - {8AC61B6E-BEC1-482D-A043-C65D2D343B35} Qwiq.Core @@ -365,19 +97,11 @@ {db07e690-4b77-414f-91c7-1a48c9f01f24} Qwiq.Mocks - - {57407CCA-8444-4713-95E9-CFC1168D846B} - Upda + + {B45C92B0-AC36-409D-86A5-5428C87384C3} + Qwiq.Tests.Common - - - - - - - - @@ -402,13 +126,9 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/packages.config b/src/Qwiq.Core.Soap/packages.config new file mode 100644 index 00000000..696d0b0c --- /dev/null +++ b/src/Qwiq.Core.Soap/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Core/ClientType.cs b/src/Qwiq.Core/ClientType.cs new file mode 100644 index 00000000..576cb181 --- /dev/null +++ b/src/Qwiq.Core/ClientType.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Qwiq +{ + public enum ClientType : short + { + Default = 0, + + Soap = 0, + + Rest = 1 + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs index 0d388804..b6678b6c 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Qwiq.Credentials { @@ -18,6 +19,9 @@ public AuthenticationOptions(string uri) public AuthenticationOptions(Uri uri, AuthenticationType authenticationType = AuthenticationType.All, ClientType clientType = ClientType.Default) { + if (uri == null) throw new ArgumentNullException(nameof(uri)); + + AuthenticationType = authenticationType; ClientType = clientType; Notifications = new CredentialsNotifications(); @@ -27,5 +31,42 @@ public AuthenticationOptions(Uri uri, AuthenticationType authenticationType = Au public CredentialsNotifications Notifications { get; set; } public Func> CreateCredentials { get; set; } + + public IEnumerable Credentials + { + get + { + foreach (var credential in EnumerateCredentials(this, AuthenticationType.OAuth)) yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationType.PersonalAccessToken)) + yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationType.Basic)) yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationType.Windows)) + yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationType.Anonymous)) + yield return credential; + } + } + + private static IEnumerable EnumerateCredentials( + AuthenticationOptions authenticationOptions, + AuthenticationType authenticationType) + { + if (!authenticationOptions.AuthenticationType.HasFlag(authenticationType)) yield break; + + var credentials = Enumerable.Empty(); + + try + { + credentials = authenticationOptions.CreateCredentials(authenticationType); + } + catch (Exception e) + { + authenticationOptions.Notifications.AuthenticationFailed( + new AuthenticationFailedNotification(null) { Exception = e }); + throw; + } + + foreach (var credential in credentials) yield return credential; + } } -} \ No newline at end of file +} diff --git a/src/Qwiq.Core/Proxies/FieldDefinitionComparer.cs b/src/Qwiq.Core/FieldDefinitionComparer.cs similarity index 100% rename from src/Qwiq.Core/Proxies/FieldDefinitionComparer.cs rename to src/Qwiq.Core/FieldDefinitionComparer.cs diff --git a/src/Qwiq.Core/IField.cs b/src/Qwiq.Core/IField.cs index a79f5455..62588528 100644 --- a/src/Qwiq.Core/IField.cs +++ b/src/Qwiq.Core/IField.cs @@ -1,5 +1,3 @@ -using Microsoft.Qwiq.Proxies; - namespace Microsoft.Qwiq { public interface IField diff --git a/src/Qwiq.Core/IQueryFactory.cs b/src/Qwiq.Core/IQueryFactory.cs index eee6d046..18a1fd4a 100644 --- a/src/Qwiq.Core/IQueryFactory.cs +++ b/src/Qwiq.Core/IQueryFactory.cs @@ -8,6 +8,8 @@ internal interface IQueryFactory IQuery Create(string wiql, bool dayPrecision = false); IQuery Create(IEnumerable ids, string wiql); + + IQuery Create(IEnumerable ids, DateTime? asOf = null); } } diff --git a/src/Qwiq.Core/IRevision.cs b/src/Qwiq.Core/IRevision.cs index 3b4db1e4..1eb6f5e6 100644 --- a/src/Qwiq.Core/IRevision.cs +++ b/src/Qwiq.Core/IRevision.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Microsoft.Qwiq.Proxies; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index d3b2fcbc..a3216818 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -21,10 +21,5 @@ public interface ITfsTeamProjectCollection TimeZone TimeZone { get; } } - internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection, IDisposable - { - T GetService(); - - T GetClient(); - } + } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemStoreFactory.cs b/src/Qwiq.Core/IWorkItemStoreFactory.cs new file mode 100644 index 00000000..c5c553fd --- /dev/null +++ b/src/Qwiq.Core/IWorkItemStoreFactory.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Qwiq.Credentials; + +namespace Microsoft.Qwiq +{ + public interface IWorkItemStoreFactory + { + IWorkItemStore Create(AuthenticationOptions options); + + [Obsolete( + "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", + false)] + IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default); + + [Obsolete( + "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", + false)] + IWorkItemStore Create( + Uri endpoint, + IEnumerable credentials, + ClientType type = ClientType.Default); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/NodeComparer.cs b/src/Qwiq.Core/NodeComparer.cs similarity index 98% rename from src/Qwiq.Core/Proxies/NodeComparer.cs rename to src/Qwiq.Core/NodeComparer.cs index 814a7690..85abaf4c 100644 --- a/src/Qwiq.Core/Proxies/NodeComparer.cs +++ b/src/Qwiq.Core/NodeComparer.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class NodeComparer : GenericComparer { diff --git a/src/Qwiq.Core/Proxies/ProjectComparer.cs b/src/Qwiq.Core/ProjectComparer.cs similarity index 80% rename from src/Qwiq.Core/Proxies/ProjectComparer.cs rename to src/Qwiq.Core/ProjectComparer.cs index fff3202d..03764718 100644 --- a/src/Qwiq.Core/Proxies/ProjectComparer.cs +++ b/src/Qwiq.Core/ProjectComparer.cs @@ -1,8 +1,8 @@ +using Microsoft.Qwiq.Proxies; using System; -using System.Collections.Generic; using System.Linq; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class ProjectComparer : GenericComparer { @@ -15,10 +15,10 @@ public override bool Equals(IProject x, IProject y) if (ReferenceEquals(y, null)) return false; return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) - && x.Guid.Equals(y.Guid) - && Equals(x.AreaRootNodes, y.AreaRootNodes) - && Equals(x.IterationRootNodes, y.IterationRootNodes) - && x.WorkItemTypes.All(p => y.WorkItemTypes.Contains(p, WorkItemTypeComparer.Instance)); + && x.Guid.Equals(y.Guid) + && Equals(x.AreaRootNodes, y.AreaRootNodes) + && Equals(x.IterationRootNodes, y.IterationRootNodes) + && x.WorkItemTypes.All(p => y.WorkItemTypes.Contains(p, WorkItemTypeComparer.Instance)); } public override int GetHashCode(IProject obj) @@ -33,8 +33,6 @@ public override int GetHashCode(IProject obj) return obj.WorkItemTypes.Aggregate(hash, (current, wit) => (13 * current) ^ wit.GetHashCode()); } - - } private class Nested diff --git a/src/Qwiq.Core/Properties/AssemblyInfo.cs b/src/Qwiq.Core/Properties/AssemblyInfo.cs index ac536c8c..b8ad5bd8 100644 --- a/src/Qwiq.Core/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core/Properties/AssemblyInfo.cs @@ -27,4 +27,5 @@ [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Mocks")] [assembly: InternalsVisibleTo("Qwiq.Mocks")] - +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Soap")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Rest")] diff --git a/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs b/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs index 2d146e05..21c228b7 100644 --- a/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs +++ b/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs @@ -1,10 +1,10 @@ namespace Microsoft.Qwiq.Proxies { - internal partial class FieldDefinitionProxy : IFieldDefinition + internal class FieldDefinitionProxy : IFieldDefinition { - public string Name { get; } + public string Name { get; internal set; } - public string ReferenceName { get; } + public string ReferenceName { get; internal set; } public override bool Equals(object obj) { diff --git a/src/Qwiq.Core/Proxies/NodeProxy.cs b/src/Qwiq.Core/Proxies/NodeProxy.cs index 39524e74..d94a0557 100644 --- a/src/Qwiq.Core/Proxies/NodeProxy.cs +++ b/src/Qwiq.Core/Proxies/NodeProxy.cs @@ -3,29 +3,25 @@ namespace Microsoft.Qwiq.Proxies { - internal partial class NodeProxy : INode + internal class NodeProxy : INode, IComparer, IEquatable { - private NodeProxy() - { - } - - public IEnumerable ChildNodes { get; } + public IEnumerable ChildNodes { get; internal set; } - public bool HasChildNodes { get; } + public bool HasChildNodes { get; internal set; } - public int Id { get; } + public int Id { get; internal set; } - public bool IsAreaNode { get; } + public bool IsAreaNode { get; internal set; } - public bool IsIterationNode { get; } + public bool IsIterationNode { get; internal set; } - public string Name { get; } + public string Name { get; internal set; } public INode ParentNode { get; internal set; } - public string Path { get; } + public string Path { get; internal set; } - public Uri Uri { get; } + public Uri Uri { get; internal set; } public override bool Equals(object obj) { @@ -37,6 +33,16 @@ public override int GetHashCode() return NodeComparer.Instance.GetHashCode(this); } + public int Compare(INode x, INode y) + { + return NodeComparer.Instance.Compare(x, y); + } + + public bool Equals(INode other) + { + return NodeComparer.Instance.Equals(this, other); + } + public override string ToString() { return Path; diff --git a/src/Qwiq.Core/Proxies/ProjectProxy.cs b/src/Qwiq.Core/Proxies/ProjectProxy.cs index f488d188..074bf929 100644 --- a/src/Qwiq.Core/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core/Proxies/ProjectProxy.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq.Proxies { - internal partial class ProjectProxy : IProject + internal class ProjectProxy : IProject, IComparer, IEquatable { private readonly Lazy> _area; @@ -35,6 +35,16 @@ private ProjectProxy() { } + public int Compare(IProject x, IProject y) + { + return ProjectComparer.Instance.Compare(x, y); + } + + public bool Equals(IProject other) + { + return ProjectComparer.Instance.Equals(this, other); + } + public IEnumerable AreaRootNodes => _area.Value; public Guid Guid { get; } diff --git a/src/Qwiq.Core/Proxies/Rest/QueryFactory.cs b/src/Qwiq.Core/Proxies/Rest/QueryFactory.cs deleted file mode 100644 index a9ec55bc..00000000 --- a/src/Qwiq.Core/Proxies/Rest/QueryFactory.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; - -using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.WorkItemTracking.Client.Wiql; - -namespace Microsoft.Qwiq.Proxies.Rest -{ - internal class QueryFactory : IQueryFactory - { - private readonly WorkItemStoreProxy _store; - - private QueryFactory(WorkItemStoreProxy store) - { - _store = store ?? throw new ArgumentNullException(nameof(store)); - } - - public IQuery Create(string wiql, bool dayPrecision) - { - try - { - var p = Parser.ParseSyntax(wiql); - return ExceptionHandlingDynamicProxyFactory.Create(new QueryProxy(p, _store)); - } - catch (SyntaxException e) - { - throw new ValidationException(e); - } - } - - public IQuery Create(IEnumerable ids, string wiql) - { - if (ids == null) throw new ArgumentNullException(nameof(ids)); - if (wiql == null) throw new ArgumentNullException(nameof(wiql)); - - try - { - var p = Parser.ParseSyntax(wiql); - if (p.Where != null || p.OrderBy != null) - throw new ValidationException( - "The WHERE and ORDER BY clauses of a query string are not supported on a parameterized query."); - - // TODO: Check that the WIQL is not a LINK or TREE query - - // We will use the SELECT information to determine the fields to return - - return ExceptionHandlingDynamicProxyFactory.Create(new QueryProxy(ids, p, _store)); - } - catch (SyntaxException e) - { - throw new ValidationException(e); - } - } - - public static IQueryFactory GetInstance(WorkItemStoreProxy store) - { - return new QueryFactory(store); - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/Rest/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core/Proxies/Rest/WorkItemLinkTypeEndProxy.cs deleted file mode 100644 index 4aeddd1f..00000000 --- a/src/Qwiq.Core/Proxies/Rest/WorkItemLinkTypeEndProxy.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - -namespace Microsoft.Qwiq.Proxies -{ - internal partial class WorkItemLinkTypeEndProxy - { - public WorkItemLinkTypeEndProxy(WorkItemRelationType item) - { - ImmutableName = item.ReferenceName; - Name = item.Name; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs index e66f62b7..6dc2d7a1 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs @@ -2,22 +2,27 @@ namespace Microsoft.Qwiq.Proxies { - internal partial class WorkItemLinkInfoProxy : IWorkItemLinkInfo + internal class WorkItemLinkInfoProxy : IWorkItemLinkInfo, IEquatable { private readonly Lazy _id; - private WorkItemLinkInfoProxy(Lazy id) + internal WorkItemLinkInfoProxy(Lazy id) { _id = id; } - public bool IsLocked { get; } + public bool Equals(IWorkItemLinkInfo other) + { + return WorkItemLinkInfoComparer.Instance.Equals(this, other); + } + + public bool IsLocked { get; internal set; } public int LinkTypeId => _id.Value; - public int SourceId { get; } + public int SourceId { get; internal set; } - public int TargetId { get; } + public int TargetId { get; internal set; } public static bool operator !=(WorkItemLinkInfoProxy x, WorkItemLinkInfoProxy y) { @@ -31,8 +36,7 @@ private WorkItemLinkInfoProxy(Lazy id) public override bool Equals(object obj) { - if (!(obj is IWorkItemLinkInfo)) return false; - return WorkItemLinkInfoComparer.Instance.Equals(this, (IWorkItemLinkInfo)obj); + return WorkItemLinkInfoComparer.Instance.Equals(this, obj as IWorkItemLinkInfo); } public override int GetHashCode() diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs index 6856bd16..f597e521 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs @@ -48,13 +48,21 @@ IEnumerator IEnumerable.GetEnumerator() return _mapByName.Values.GetEnumerator(); } - public bool Contains(string linkTypeName) + public bool Contains(string linkTypeEndName) { - return _mapByName.ContainsKey(linkTypeName); + if (string.IsNullOrWhiteSpace(linkTypeEndName)) return false; + + return _mapByName.ContainsKey(linkTypeEndName); } public bool TryGetByName(string linkTypeEndName, out IWorkItemLinkTypeEnd linkTypeEnd) { + if (string.IsNullOrWhiteSpace(linkTypeEndName)) + { + linkTypeEnd = null; + return false; + } + return _mapByName.TryGetValue(linkTypeEndName, out linkTypeEnd); } } diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs index 38f2c8e3..a72616f8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Proxies { - internal partial class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd + internal class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd { private readonly Lazy _opposite; @@ -12,7 +12,7 @@ internal WorkItemLinkTypeEndProxy(Lazy oppositeEnd) _opposite = oppositeEnd; } - private WorkItemLinkTypeEndProxy() + internal WorkItemLinkTypeEndProxy() { _opposite = new Lazy( () => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs index 0398a218..5094641b 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs @@ -2,15 +2,15 @@ namespace Microsoft.Qwiq.Proxies { - internal partial class WorkItemLinkTypeProxy : IWorkItemLinkType + internal class WorkItemLinkTypeProxy : IWorkItemLinkType { private readonly Lazy _forwardFac; private readonly Lazy _reverseFac; - private IWorkItemLinkTypeEnd _forward; + protected IWorkItemLinkTypeEnd _forward; - private IWorkItemLinkTypeEnd _reverse; + protected IWorkItemLinkTypeEnd _reverse; internal WorkItemLinkTypeProxy(IWorkItemLinkTypeEnd forward, IWorkItemLinkTypeEnd reverse) { @@ -24,7 +24,7 @@ internal WorkItemLinkTypeProxy(Lazy forward, Lazy _reverse ?? _reverseFac.Value; diff --git a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs index 24e767a6..76d59b9a 100644 --- a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Proxies { - public partial class WorkItemTypeProxy : IWorkItemType, IEquatable + public class WorkItemTypeProxy : IWorkItemType, IEquatable { private readonly Lazy _fieldDefinitions; @@ -14,6 +14,8 @@ internal WorkItemTypeProxy( Lazy fieldDefinitions, Func workItemFactory) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + _fieldDefinitions = fieldDefinitions; _workItemFactory = workItemFactory; Name = name; diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index b210be0f..73ce1d6d 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -202,6 +202,7 @@ + @@ -224,6 +225,7 @@ + @@ -252,88 +254,39 @@ ITeamFoundationIdentity.cs - + - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + @@ -345,6 +298,7 @@ + diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkInfoComparer.cs b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs similarity index 95% rename from src/Qwiq.Core/Proxies/WorkItemLinkInfoComparer.cs rename to src/Qwiq.Core/WorkItemLinkInfoComparer.cs index f081befd..e3ee8269 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkInfoComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class WorkItemLinkInfoComparer : GenericComparer { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs similarity index 98% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeComparer.cs rename to src/Qwiq.Core/WorkItemLinkTypeComparer.cs index 3575b581..102561e8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class WorkItemLinkTypeComparer : GenericComparer { diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs similarity index 97% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeEndComparer.cs rename to src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs index d97bd00c..200af154 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class WorkItemLinkTypeEndComparer : GenericComparer { diff --git a/src/Qwiq.Core/WorkItemStoreFactory.cs b/src/Qwiq.Core/WorkItemStoreFactory.cs deleted file mode 100644 index 266a4e13..00000000 --- a/src/Qwiq.Core/WorkItemStoreFactory.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Exceptions; -using Microsoft.Qwiq.Proxies; -using Microsoft.TeamFoundation; -using Microsoft.TeamFoundation.Build.WebApi; -using Microsoft.TeamFoundation.Client; -using Microsoft.VisualStudio.Services.Common; - -using TfsSoap = Microsoft.Qwiq.Proxies.Soap; -using TfsRest = Microsoft.Qwiq.Proxies.Rest; - -namespace Microsoft.Qwiq -{ - public enum ClientType : short - { - Default = 0, - - Soap = 0, - - Rest = 1 - } - - public interface IWorkItemStoreFactory - { - IWorkItemStore Create(AuthenticationOptions options); - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default); - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - IWorkItemStore Create( - Uri endpoint, - IEnumerable credentials, - ClientType type = ClientType.Default); - } - - public class WorkItemStoreFactory : IWorkItemStoreFactory - { - private static readonly Lazy Instance = - new Lazy(() => new WorkItemStoreFactory()); - - private WorkItemStoreFactory() - { - } - - public IWorkItemStore Create(AuthenticationOptions options) - { - var credentials = EnumerateCredentials(options); - - foreach (var credential in credentials) - try - { - var tfsNative = ConnectToTfsCollection(options.Uri, credential.Credentials); - var tfsProxy = - ExceptionHandlingDynamicProxyFactory.Create( - new TfsTeamProjectCollectionProxy(tfsNative)); - - options.Notifications.AuthenticationSuccess( - new AuthenticationSuccessNotification(credential, tfsProxy)); - - IWorkItemStore wis; - switch (options.ClientType) - { - case ClientType.Rest: - wis = CreateRestWorkItemStore(tfsNative); - break; - - case ClientType.Soap: - wis = CreateSoapWorkItemStore(tfsNative); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(options.ClientType)); - } - - return ExceptionHandlingDynamicProxyFactory.Create(wis); - } - catch (TeamFoundationServerUnauthorizedException e) - { - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); - } - - var nocreds = new AccessDeniedException("Invalid credentials"); - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); - throw nocreds; - } - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default) - { - return Create(endpoint, new[] { credentials }, type); - } - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - public IWorkItemStore Create( - Uri endpoint, - IEnumerable credentials, - ClientType type = ClientType.Default) - { - var options = - new AuthenticationOptions( - endpoint, - AuthenticationType.Windows) { CreateCredentials = t => credentials }; - - return Create(options); - } - - public static IWorkItemStoreFactory GetInstance() - { - return Instance.Value; - } - - private static TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) - { - var tfsServer = new TfsTeamProjectCollection(endpoint, credentials); - tfsServer.EnsureAuthenticated(); - return tfsServer; - } - - private static IWorkItemStore CreateRestWorkItemStore(TfsTeamProjectCollection tfs) - { - return new TfsRest.WorkItemStoreProxy(tfs, TfsRest.QueryFactory.GetInstance); - } - - private static IWorkItemStore CreateSoapWorkItemStore(TfsTeamProjectCollection tfs) - { - return new TfsSoap.WorkItemStoreProxy(tfs, TfsSoap.QueryFactory.GetInstance); - } - - private static IEnumerable EnumerateCredentials(AuthenticationOptions options) - { - foreach (var credential in EnumerateCredentials(options, AuthenticationType.OAuth)) yield return credential; - foreach (var credential in EnumerateCredentials(options, AuthenticationType.PersonalAccessToken)) - yield return credential; - foreach (var credential in EnumerateCredentials(options, AuthenticationType.Basic)) yield return credential; - foreach (var credential in EnumerateCredentials(options, AuthenticationType.Windows)) - yield return credential; - foreach (var credential in EnumerateCredentials(options, AuthenticationType.Anonymous)) - yield return credential; - } - - private static IEnumerable EnumerateCredentials( - AuthenticationOptions authenticationOptions, - AuthenticationType authenticationType) - { - if (!authenticationOptions.AuthenticationType.HasFlag(authenticationType)) yield break; - - var credentials = Enumerable.Empty(); - - try - { - credentials = authenticationOptions.CreateCredentials(authenticationType); - } - catch (Exception e) - { - authenticationOptions.Notifications.AuthenticationFailed( - new AuthenticationFailedNotification(null) { Exception = e }); - throw; - } - - foreach (var credential in credentials) yield return credential; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/WorkItemTypeComparer.cs b/src/Qwiq.Core/WorkItemTypeComparer.cs similarity index 97% rename from src/Qwiq.Core/Proxies/WorkItemTypeComparer.cs rename to src/Qwiq.Core/WorkItemTypeComparer.cs index 8fdb818f..7e3850c9 100644 --- a/src/Qwiq.Core/Proxies/WorkItemTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeComparer.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class WorkItemTypeComparer : GenericComparer { diff --git a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs index 0297115b..042e9cd4 100644 --- a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs @@ -1,5 +1,4 @@ -using Microsoft.Qwiq.Proxies.Soap; -using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Soap.Proxies; using Microsoft.Qwiq.Tests.Common; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs index b869c59a..ff5a8cf2 100644 --- a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; -using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Soap.Proxies; using Microsoft.Qwiq.Tests.Common; -using Microsoft.Qwiq.Proxies.Soap; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; diff --git a/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs b/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs index e9a1581f..f617a1dd 100644 --- a/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs +++ b/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs @@ -9,7 +9,7 @@ namespace Microsoft.Qwiq.Core.Tests { - public abstract class IntegrationContextSpecification : ContextSpecification + public abstract class IntegrationContextSpecificationSpecification : WorkItemStoreComparisonContextSpecification { protected Result RestResult { get; set; } @@ -98,12 +98,10 @@ public void Description_is_equal() public override void Given() { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); - var fac = WorkItemStoreFactory.GetInstance(); - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + base.Given(); - SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; - RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + SoapResult = new Result() { WorkItemStore = Soap }; + RestResult = new Result() { WorkItemStore = Rest }; } [TestMethod] diff --git a/test/Qwiq.Core.Tests/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Core.Tests/LargeWiqlHierarchyQueryTests.cs index bfd74864..d63db282 100644 --- a/test/Qwiq.Core.Tests/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Core.Tests/LargeWiqlHierarchyQueryTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class LargeWiqlHierarchyQueryTests : ContextSpecification + public class LargeWiqlHierarchyQueryTests : WorkItemStoreComparisonContextSpecification { protected Result RestResult { get; set; } @@ -19,12 +19,10 @@ public class LargeWiqlHierarchyQueryTests : ContextSpecification public override void Given() { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); - var fac = WorkItemStoreFactory.GetInstance(); - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + base.Given(); - SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; - RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + SoapResult = new Result() { WorkItemStore = Soap }; + RestResult = new Result() { WorkItemStore = Rest }; } public override void When() diff --git a/test/Qwiq.Core.Tests/LinkTypeTests.cs b/test/Qwiq.Core.Tests/LinkTypeTests.cs index 25cc8890..cf43ee0a 100644 --- a/test/Qwiq.Core.Tests/LinkTypeTests.cs +++ b/test/Qwiq.Core.Tests/LinkTypeTests.cs @@ -10,28 +10,20 @@ namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class LinkTypeTests : ContextSpecification + public class LinkTypeTests : WorkItemStoreComparisonContextSpecification { - private IWorkItemStore _soap; + private IEnumerable _soapResult; - private IWorkItemStore _rest; + private IEnumerable _restResult; - public override void Given() - { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); - var fac = WorkItemStoreFactory.GetInstance(); - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - - _soap = fac.Create(uri, credentials, ClientType.Soap); - _rest = fac.Create(uri, credentials, ClientType.Rest); - } + public override void When() { - _soapResult = _soap.WorkItemLinkTypes.ToList(); - _restResult = _rest.WorkItemLinkTypes.ToList(); + _soapResult = Soap.WorkItemLinkTypes.ToList(); + _restResult = Rest.WorkItemLinkTypes.ToList(); } [TestMethod] diff --git a/test/Qwiq.Core.Tests/Mocks/MockQueryFactory.cs b/test/Qwiq.Core.Tests/Mocks/MockQueryFactory.cs index 605048d4..f557a51d 100644 --- a/test/Qwiq.Core.Tests/Mocks/MockQueryFactory.cs +++ b/test/Qwiq.Core.Tests/Mocks/MockQueryFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Microsoft.Qwiq.Core.Tests.Mocks @@ -17,6 +18,17 @@ public IQuery Create(IEnumerable ids, string wiql) return new MockQuery(wiql, false, _wiqls); } + public IQuery Create(IEnumerable ids, DateTime? asOf = null) + { + var wiql = "SELECT * FROM WorkItems"; + if (asOf.HasValue) + { + wiql += $" ASOF \'{asOf.Value:u}\'"; + } + + return Create(ids, wiql); + } + public int CreateCallCount { get; private set; } public IEnumerable QueryWiqls => _wiqls; diff --git a/test/Qwiq.Core.Tests/MultipleIdTests.cs b/test/Qwiq.Core.Tests/MultipleIdTests.cs index bf808e52..37c67d4f 100644 --- a/test/Qwiq.Core.Tests/MultipleIdTests.cs +++ b/test/Qwiq.Core.Tests/MultipleIdTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class MultipleIdTests : IntegrationContextSpecification + public class MultipleIdTests : IntegrationContextSpecificationSpecification { private const int Id = 10726528; diff --git a/test/Qwiq.Core.Tests/ProjectTests.cs b/test/Qwiq.Core.Tests/ProjectTests.cs index 6b8ad1a4..baa98c28 100644 --- a/test/Qwiq.Core.Tests/ProjectTests.cs +++ b/test/Qwiq.Core.Tests/ProjectTests.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Linq; -using Microsoft.Qwiq.Proxies; using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,35 +11,23 @@ namespace Microsoft.Qwiq.Core.Tests { - public abstract class ProjectContextSpecification : ContextSpecification + public abstract class ProjectContextSpecificationSpecification : WorkItemStoreComparisonContextSpecification { protected List RestProjects { get; set; } protected List SoapProjects { get; set; } - protected IWorkItemStore Rest { get; set; } - protected IWorkItemStore Soap { get; set; } public override void When() { RestProjects = Rest.Projects.ToList(); SoapProjects = Soap.Projects.ToList(); } - - public override void Given() - { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); - var fac = WorkItemStoreFactory.GetInstance(); - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - - Soap = fac.Create(uri, credentials, ClientType.Soap); - Rest = fac.Create(uri, credentials, ClientType.Rest); - } } [TestClass] - public class Given_projects_from_each_WorkItemStore_implementation : ProjectContextSpecification + public class Given_projects_from_each_WorkItemStore_implementation : ProjectContextSpecificationSpecification { [TestMethod] [TestCategory("localOnly")] diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index a2d9e4d2..fa30dc71 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -241,6 +241,7 @@ + @@ -253,6 +254,14 @@ {8ac61b6e-bec1-482d-a043-c65d2d343b35} Qwiq.Core + + {0f70e1c2-d696-4749-8601-374a7c9c268a} + Qwiq.Core.Rest + + + {6f5ffc42-0539-4161-b348-a54adb57c2bd} + Qwiq.Core.Soap + {B45C92B0-AC36-409D-86A5-5428C87384C3} Qwiq.Tests.Common diff --git a/test/Qwiq.Core.Tests/SingleIdTests.cs b/test/Qwiq.Core.Tests/SingleIdTests.cs index 08aa691c..7164caa8 100644 --- a/test/Qwiq.Core.Tests/SingleIdTests.cs +++ b/test/Qwiq.Core.Tests/SingleIdTests.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class SingleIdTests : IntegrationContextSpecification + public class SingleIdTests : IntegrationContextSpecificationSpecification { private const int Id = 10726528; diff --git a/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs b/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs index c07e3535..1b68523d 100644 --- a/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs +++ b/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class WiqlFlatQueryTests : IntegrationContextSpecification + public class WiqlFlatQueryTests : IntegrationContextSpecificationSpecification { private const int Id = 10726528; diff --git a/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs b/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs index 93d870c8..1a7f70f8 100644 --- a/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class WiqlHierarchyQueryTests : ContextSpecification + public class WiqlHierarchyQueryTests : WorkItemStoreComparisonContextSpecification { protected Result RestResult { get; set; } @@ -19,12 +19,10 @@ public class WiqlHierarchyQueryTests : ContextSpecification public override void Given() { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); - var fac = WorkItemStoreFactory.GetInstance(); - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + base.Given(); - SoapResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Soap) }; - RestResult = new Result() { WorkItemStore = fac.Create(uri, credentials, ClientType.Rest) }; + SoapResult = new Result() { WorkItemStore = Soap }; + RestResult = new Result() { WorkItemStore = Rest }; } public override void When() diff --git a/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs new file mode 100644 index 00000000..4f496fe8 --- /dev/null +++ b/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs @@ -0,0 +1,22 @@ +using System; + +using Microsoft.Qwiq.Tests.Common; + +namespace Microsoft.Qwiq.Core.Tests +{ + public abstract class WorkItemStoreComparisonContextSpecification : ContextSpecification + { + protected IWorkItemStore Rest { get; private set; } + + protected IWorkItemStore Soap { get; private set; } + + public override void Given() + { + var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + Soap = Microsoft.Qwiq.Soap.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Soap); + Rest = Microsoft.Qwiq.Rest.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Rest); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs index a824cc5c..7cfccf7b 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Qwiq.Credentials; +using Microsoft.Qwiq.Soap; using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.Services.Client; using Microsoft.VisualStudio.Services.Common; @@ -18,7 +19,7 @@ public abstract class WorkItemStoreFactoryContextSpecification : ContextSpecific public override void Given() { base.Given(); - Instance = WorkItemStoreFactory.GetInstance(); + Instance = WorkItemStoreFactory.Instance; } public override void Cleanup() diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 6c53443a..0565efa4 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -4,39 +4,19 @@ using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Proxies; +using Microsoft.Qwiq.Soap; +using Microsoft.Qwiq.Soap.Proxies; using Microsoft.Qwiq.Tests.Common; using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; -using TfsSoap = Microsoft.Qwiq.Proxies.Soap; - namespace Microsoft.Qwiq.Core.Tests { - - public abstract class WorkItemStoreComparisonContext : ContextSpecification - { - protected IWorkItemStore Rest { get; set; } - - protected IWorkItemStore Soap { get; set; } - - public override void Given() - { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); - var fac = WorkItemStoreFactory.GetInstance(); - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - - Soap = fac.Create(uri, credentials, ClientType.Soap); - Rest = fac.Create(uri, credentials, ClientType.Rest); - } - } - [TestClass] - public class Given_SOAP_and_REST_workitemstore_implementations : WorkItemStoreComparisonContext + public class Given_SOAP_and_REST_workitemstore_implementations : WorkItemStoreComparisonContextSpecification { - - public override void When() { SoapLinkTypes = Soap.WorkItemLinkTypes; @@ -179,11 +159,11 @@ public override void When() } } - public abstract class WorkItemStoreSoapTests : WorkItemStoreTests + public abstract class WorkItemStoreSoapTests : WorkItemStoreTests { - protected override TfsSoap.WorkItemStoreProxy Create() + protected override IWorkItemStore Create() { - return new TfsSoap.WorkItemStoreProxy((TfsTeamProjectCollection)null, s => QueryFactory); + return new WorkItemStoreProxy((TfsTeamProjectCollection)null, s => QueryFactory); } } diff --git a/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs b/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs index 316d6511..3cbc5cd6 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs @@ -1,5 +1,3 @@ -using Microsoft.Qwiq.Proxies; - namespace Microsoft.Qwiq.Mocks { public class MockWorkItemLinkInfo : IWorkItemLinkInfo From 3843028f5b98738dac064383c5fd752f6dcaef66 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 12:08:47 -0700 Subject: [PATCH 056/251] Use Roslyn compiler package Changes from 2e79f507580bb405587ca6f53d52f9728a652d0a were reverted at some point. --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 2 ++ src/Qwiq.Core.Rest/packages.config | 1 + src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 2 ++ src/Qwiq.Core.Soap/packages.config | 1 + src/Qwiq.Core/app.config | 6 +++++- src/Qwiq.Core/packages.config | 1 + src/Qwiq.Identity/app.config | 8 ++++++++ src/Qwiq.Identity/packages.config | 1 + src/Qwiq.Linq/app.config | 8 ++++++++ src/Qwiq.Linq/packages.config | 1 + src/Qwiq.Mapper/app.config | 8 ++++++++ src/Qwiq.Mapper/packages.config | 1 + src/Qwiq.Relatives/app.config | 8 ++++++++ src/Qwiq.Relatives/packages.config | 1 + test/Qwiq.Core.Tests/packages.config | 1 + .../Qwiq.Identity.Benchmark.Tests.csproj | 8 ++++++++ test/Qwiq.Identity.Benchmark.Tests/packages.config | 1 + test/Qwiq.Identity.Tests/packages.config | 1 + test/Qwiq.Linq.Tests/packages.config | 1 + .../Qwiq.Mapper.Benchmark.Tests.csproj | 2 ++ test/Qwiq.Mapper.Benchmark.Tests/packages.config | 1 + test/Qwiq.Mapper.Tests/packages.config | 1 + test/Qwiq.Mocks/packages.config | 1 + test/Qwiq.Relatives.Tests/packages.config | 1 + test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj | 2 ++ test/Qwiq.Tests.Common/packages.config | 1 + 26 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index fa046fd0..580702de 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -1,5 +1,6 @@  + Debug @@ -137,5 +138,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/packages.config b/src/Qwiq.Core.Rest/packages.config index 68f75433..694c08ef 100644 --- a/src/Qwiq.Core.Rest/packages.config +++ b/src/Qwiq.Core.Rest/packages.config @@ -2,6 +2,7 @@ + diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 4618e500..834c1f3e 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -1,5 +1,6 @@  + Debug @@ -258,6 +259,7 @@ + \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/packages.config b/src/Qwiq.Core.Soap/packages.config index 696d0b0c..49b2771b 100644 --- a/src/Qwiq.Core.Soap/packages.config +++ b/src/Qwiq.Core.Soap/packages.config @@ -4,6 +4,7 @@ + diff --git a/src/Qwiq.Core/app.config b/src/Qwiq.Core/app.config index 2909d3ec..0a346744 100644 --- a/src/Qwiq.Core/app.config +++ b/src/Qwiq.Core/app.config @@ -8,7 +8,11 @@ - + + + + + diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index b0941d25..c2cef98d 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -5,6 +5,7 @@ + diff --git a/src/Qwiq.Identity/app.config b/src/Qwiq.Identity/app.config index 9b678d31..556f3897 100644 --- a/src/Qwiq.Identity/app.config +++ b/src/Qwiq.Identity/app.config @@ -6,6 +6,14 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index e053860d..90e22b51 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -5,6 +5,7 @@ + diff --git a/src/Qwiq.Linq/app.config b/src/Qwiq.Linq/app.config index 9b678d31..556f3897 100644 --- a/src/Qwiq.Linq/app.config +++ b/src/Qwiq.Linq/app.config @@ -6,6 +6,14 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index c9025c48..f3ebd447 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -4,6 +4,7 @@ + diff --git a/src/Qwiq.Mapper/app.config b/src/Qwiq.Mapper/app.config index 9b678d31..556f3897 100644 --- a/src/Qwiq.Mapper/app.config +++ b/src/Qwiq.Mapper/app.config @@ -6,6 +6,14 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index e053860d..90e22b51 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -5,6 +5,7 @@ + diff --git a/src/Qwiq.Relatives/app.config b/src/Qwiq.Relatives/app.config index 9b678d31..556f3897 100644 --- a/src/Qwiq.Relatives/app.config +++ b/src/Qwiq.Relatives/app.config @@ -6,6 +6,14 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Relatives/packages.config b/src/Qwiq.Relatives/packages.config index c9025c48..f3ebd447 100644 --- a/src/Qwiq.Relatives/packages.config +++ b/src/Qwiq.Relatives/packages.config @@ -4,6 +4,7 @@ + diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index 7b915861..5f244b9a 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -5,6 +5,7 @@ + diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index 7b660f50..d4a823dd 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -12,6 +13,7 @@ v4.6 512 + true @@ -155,4 +157,10 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/packages.config b/test/Qwiq.Identity.Benchmark.Tests/packages.config index 2e4d7312..808adae9 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/packages.config +++ b/test/Qwiq.Identity.Benchmark.Tests/packages.config @@ -6,6 +6,7 @@ + diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index 3d41f8c9..f4e9d3ed 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index 3d41f8c9..f4e9d3ed 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index 87c612f9..dd087094 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -304,5 +305,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 1c296451..9a3cfea3 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -9,6 +9,7 @@ + diff --git a/test/Qwiq.Mapper.Tests/packages.config b/test/Qwiq.Mapper.Tests/packages.config index 3d41f8c9..f4e9d3ed 100644 --- a/test/Qwiq.Mapper.Tests/packages.config +++ b/test/Qwiq.Mapper.Tests/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/test/Qwiq.Mocks/packages.config b/test/Qwiq.Mocks/packages.config index f1ec82ec..0f26831b 100644 --- a/test/Qwiq.Mocks/packages.config +++ b/test/Qwiq.Mocks/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/test/Qwiq.Relatives.Tests/packages.config b/test/Qwiq.Relatives.Tests/packages.config index 3d41f8c9..f4e9d3ed 100644 --- a/test/Qwiq.Relatives.Tests/packages.config +++ b/test/Qwiq.Relatives.Tests/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index bf559377..9ca73e00 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -1,5 +1,6 @@  + Debug @@ -58,5 +59,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/packages.config b/test/Qwiq.Tests.Common/packages.config index 3d41f8c9..f4e9d3ed 100644 --- a/test/Qwiq.Tests.Common/packages.config +++ b/test/Qwiq.Tests.Common/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file From 08a39c5f1cf67e3b622f9385a252e00696e04614 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 12:19:11 -0700 Subject: [PATCH 057/251] Remove readme.txt from Rest and Soap core nuspec This file is included in core and does not also need to be included in these packages. --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.nuspec | 3 --- src/Qwiq.Core.Soap/Qwiq.Core.Soap.nuspec | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.nuspec b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.nuspec index ef963e74..c82f9b7b 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.nuspec +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.nuspec @@ -13,7 +13,4 @@ $copyright$ Microsoft Team Foundation Server TFS VSO Visual Studio Online VisualStudio Agile WIT Work Item Tracking Object Model VSTS TeamFoundation TFSOM - - - \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.nuspec b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.nuspec index ef963e74..c82f9b7b 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.nuspec +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.nuspec @@ -13,7 +13,4 @@ $copyright$ Microsoft Team Foundation Server TFS VSO Visual Studio Online VisualStudio Agile WIT Work Item Tracking Object Model VSTS TeamFoundation TFSOM - - - \ No newline at end of file From ace512e9dd8a70a5e66fb0035557acaa350ac07d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 12:19:33 -0700 Subject: [PATCH 058/251] Update extended client version number in readme.txt --- src/Qwiq.Core/readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core/readme.txt b/src/Qwiq.Core/readme.txt index d18a27d9..94252707 100644 --- a/src/Qwiq.Core/readme.txt +++ b/src/Qwiq.Core/readme.txt @@ -29,12 +29,12 @@ The tag can be ommitted or included depending on if you would like the visible in the project or be hidden. - + Microsoft.WITDataStore32.dll PreserveNewest False - + Microsoft.WITDataStore64.dll PreserveNewest False From a585a93480fcdc762d57428fb88effb4eaae3a60 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 13:02:32 -0700 Subject: [PATCH 059/251] Add WorkItemLinkTypeComparer unit tests --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 5 -- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 5 -- .../Proxies/WorkItemLinkTypeEndProxy.cs | 2 +- .../Proxies/WorkItemLinkTypeProxy.cs | 12 ++- src/Qwiq.Core/WorkItemLinkTypeComparer.cs | 8 +- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 5 ++ .../WorkItemLinkTypeComparerTests.cs | 81 +++++++++++++++++++ test/Qwiq.Mocks/MockWorkItemLinkType.cs | 20 ++--- test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs | 32 ++------ 9 files changed, 116 insertions(+), 54 deletions(-) create mode 100644 test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 580702de..67349a4c 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -126,11 +126,6 @@ Designer - - - readme.txt - - diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 834c1f3e..1faaac35 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -246,11 +246,6 @@ Designer - - - readme.txt - - diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs index a72616f8..7cbba208 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Proxies { - internal class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd + public class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd { private readonly Lazy _opposite; diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs index 5094641b..e05b4082 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Proxies { - internal class WorkItemLinkTypeProxy : IWorkItemLinkType + public class WorkItemLinkTypeProxy : IWorkItemLinkType, IComparable, IEquatable { private readonly Lazy _forwardFac; @@ -43,6 +43,16 @@ public override bool Equals(object obj) return WorkItemLinkTypeComparer.Instance.Equals(this, obj as IWorkItemLinkType); } + public int CompareTo(IWorkItemLinkType other) + { + return WorkItemLinkTypeComparer.Instance.Compare(this, other); + } + + public bool Equals(IWorkItemLinkType other) + { + return WorkItemLinkTypeComparer.Instance.Equals(this, other); + } + public override int GetHashCode() { return WorkItemLinkTypeComparer.Instance.GetHashCode(this); diff --git a/src/Qwiq.Core/WorkItemLinkTypeComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs index 102561e8..4b21073b 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs @@ -15,8 +15,8 @@ public override bool Equals(IWorkItemLinkType x, IWorkItemLinkType y) return x.IsActive == y.IsActive && x.IsDirectional == y.IsDirectional && string.Equals(x.ReferenceName, y.ReferenceName, StringComparison.OrdinalIgnoreCase) - && Equals(x.ForwardEnd, y.ForwardEnd) - && Equals(x.ReverseEnd, y.ReverseEnd); + && WorkItemLinkTypeEndComparer.Instance.Equals(x.ForwardEnd, y.ForwardEnd) + && WorkItemLinkTypeEndComparer.Instance.Equals(x.ReverseEnd, y.ReverseEnd); } public override int GetHashCode(IWorkItemLinkType obj) @@ -27,8 +27,8 @@ public override int GetHashCode(IWorkItemLinkType obj) hash = (13 * hash) ^ (obj.ReferenceName != null ? obj.ReferenceName.GetHashCode() : 0); hash = (13 * hash) ^ obj.IsActive.GetHashCode(); hash = (13 * hash) ^ obj.IsDirectional.GetHashCode(); - hash = (13 * hash) ^ (obj.ForwardEnd != null ? obj.ForwardEnd.GetHashCode() : 0); - hash = (13 * hash) ^ (obj.ReverseEnd != null ? obj.ReverseEnd.GetHashCode() : 0); + hash = (13 * hash) ^ (obj.ForwardEnd != null ? WorkItemLinkTypeEndComparer.Instance.GetHashCode(obj.ForwardEnd) : 0); + hash = (13 * hash) ^ (obj.ReverseEnd != null ? WorkItemLinkTypeEndComparer.Instance.GetHashCode(obj.ReverseEnd) : 0); return hash; } diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index fa30dc71..75fa507d 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -241,6 +241,7 @@ + @@ -262,6 +263,10 @@ {6f5ffc42-0539-4161-b348-a54adb57c2bd} Qwiq.Core.Soap + + {DB07E690-4B77-414F-91C7-1A48C9F01F24} + Qwiq.Mocks + {B45C92B0-AC36-409D-86A5-5428C87384C3} Qwiq.Tests.Common diff --git a/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs b/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs new file mode 100644 index 00000000..5297d4d7 --- /dev/null +++ b/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Qwiq.Mocks; +using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Core.Tests +{ + [TestClass] + public class WorkItemLinkTypeComparerTests : ContextSpecification + { + private IWorkItemLinkType _first; + + private IWorkItemLinkType _second; + + private WorkItemLinkTypeComparer _instance; + + private WorkItemLinkTypeEndComparer _instance2; + + private bool _equalityResult; + + private bool _forwardEqualityResult; + + public override void Given() + { + _instance = WorkItemLinkTypeComparer.Instance; + _instance2 = WorkItemLinkTypeEndComparer.Instance; + + _first = new MockWorkItemLinkType(CoreLinkTypeReferenceNames.Hierarchy); + _second = new MockWorkItemLinkType(CoreLinkTypeReferenceNames.Hierarchy); + } + + public override void When() + { + _equalityResult = _instance.Equals(_first, _second); + _forwardEqualityResult = _instance2.Equals(_first.ForwardEnd, _second.ForwardEnd); + } + + [TestMethod] + public void Different_object_instances_are_equal() + { + _equalityResult.ShouldBeTrue(); + } + + [TestMethod] + public void Different_object_instance_forward_linktypeend_are_equal() + { + _forwardEqualityResult.ShouldBeTrue(); + } + + [TestMethod] + public void Object_HashCodes_are_equal() + { + _first.GetHashCode().ShouldEqual(_second.GetHashCode()); + } + + [TestMethod] + public void Object_and_Comparer_HashCodes_are_equal() + { + _first.GetHashCode().ShouldEqual(_instance.GetHashCode(_first)); + } + + [TestMethod] + public void LinkTypeEnd_Object_HashCodes_are_equal() + { + _first.ForwardEnd.GetHashCode().ShouldEqual(_second.ForwardEnd.GetHashCode()); + } + + [TestMethod] + public void LinkTypeEnd_Object_and_Comparer_HashCodes_are_equal() + { + _first.ForwardEnd.GetHashCode().ShouldEqual(_instance2.GetHashCode(_first.ForwardEnd)); + } + } +} diff --git a/test/Qwiq.Mocks/MockWorkItemLinkType.cs b/test/Qwiq.Mocks/MockWorkItemLinkType.cs index e9ad6d14..cf572424 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkType.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkType.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkType : IWorkItemLinkType + public class MockWorkItemLinkType : Microsoft.Qwiq.Proxies.WorkItemLinkTypeProxy { public MockWorkItemLinkType(string referenceName) { @@ -23,7 +23,7 @@ public MockWorkItemLinkType(string referenceName) else if (CoreLinkTypeReferenceNames.Related.Equals(referenceName, StringComparison.OrdinalIgnoreCase)) { forwardName = reverseName = "Related"; - forwardId = CoreLinkTypes.Related; + forwardId = reverseId = -CoreLinkTypes.Related; IsDirectional = false; } else if (CoreLinkTypeReferenceNames.Dependency.Equals(referenceName, StringComparison.OrdinalIgnoreCase)) @@ -48,8 +48,8 @@ public MockWorkItemLinkType(string referenceName) "Reference name not supported in mock object."); } ReferenceName = referenceName; - ForwardEnd = new MockWorkItemLinkTypeEnd(this, forwardName, -forwardId); - ReverseEnd = new MockWorkItemLinkTypeEnd(this, reverseName, -reverseId); + _forward = new MockWorkItemLinkTypeEnd(this, forwardName, true, -forwardId); + _reverse = new MockWorkItemLinkTypeEnd(this, reverseName, false, -reverseId); } public MockWorkItemLinkType( @@ -63,18 +63,10 @@ public MockWorkItemLinkType( if (string.IsNullOrEmpty(reverseEndName)) throw new ArgumentException("Value cannot be null or empty.", nameof(reverseEndName)); IsDirectional = isDirectional; ReferenceName = referenceName; - ForwardEnd = new MockWorkItemLinkTypeEnd(this, forwardEndName); - ReverseEnd = new MockWorkItemLinkTypeEnd(this, reverseEndName); + _forward = new MockWorkItemLinkTypeEnd(this, forwardEndName, true); + _reverse = new MockWorkItemLinkTypeEnd(this, reverseEndName, false); } - public IWorkItemLinkTypeEnd ForwardEnd { get; } - public bool IsActive => true; - - public bool IsDirectional { get; } - - public string ReferenceName { get; } - - public IWorkItemLinkTypeEnd ReverseEnd { get; } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs b/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs index a8aea7f2..9a5b7771 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs @@ -2,44 +2,28 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkTypeEnd : IWorkItemLinkTypeEnd + public class MockWorkItemLinkTypeEnd : Microsoft.Qwiq.Proxies.WorkItemLinkTypeEndProxy { [Obsolete( - "This method has been deprecated and will be removed in a future release. See a constructor that takes (IWorkItemLinkType, String).")] + "This method has been deprecated and will be removed in a future release. See a constructor that takes (IWorkItemLinkType, String, Bool, Int32).")] public MockWorkItemLinkTypeEnd(string name) { Name = name; } - public MockWorkItemLinkTypeEnd(IWorkItemLinkType linkType, string name, int id = 0) + public MockWorkItemLinkTypeEnd(IWorkItemLinkType linkType, string name, bool isForward, int id = 0) { Id = id; LinkType = linkType ?? throw new ArgumentNullException(nameof(linkType)); Name = name ?? throw new ArgumentNullException(nameof(name)); - } - - public int Id { get; } + IsForwardLink = isForward; - public string ImmutableName - - { - get + var referenceName = LinkType.ReferenceName; + if (!LinkType.IsDirectional) { - var referenceName = LinkType.ReferenceName; - if (!LinkType.IsDirectional) - { - return referenceName; - } - return referenceName + (IsForwardLink ? "-Forward" : "-Reverse"); + ImmutableName = referenceName; } + ImmutableName = referenceName + (IsForwardLink ? "-Forward" : "-Reverse"); } - - public bool IsForwardLink => Equals(LinkType.ForwardEnd, this); - - public IWorkItemLinkType LinkType { get; } - - public string Name { get; } - - public IWorkItemLinkTypeEnd OppositeEnd => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd; } } From a4e32cdbcc8886e966bf7a94a9261a0246112127 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 13:30:20 -0700 Subject: [PATCH 060/251] Convert `MockFieldDefinition` to Core proxy --- src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs | 16 ++++++++++++-- test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 2 +- test/Qwiq.Mocks/MockFieldDefinition.cs | 22 +++++++++++-------- test/Qwiq.Mocks/MockWorkItemType.cs | 2 +- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs b/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs index 21c228b7..b7f62e8e 100644 --- a/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs +++ b/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs @@ -1,11 +1,23 @@ -namespace Microsoft.Qwiq.Proxies +using System; + +namespace Microsoft.Qwiq.Proxies { - internal class FieldDefinitionProxy : IFieldDefinition + public class FieldDefinitionProxy : IFieldDefinition, IComparable, IEquatable { public string Name { get; internal set; } public string ReferenceName { get; internal set; } + public int CompareTo(IFieldDefinition other) + { + return FieldDefinitionComparer.Instance.Compare(this, other); + } + + public bool Equals(IFieldDefinition other) + { + return FieldDefinitionComparer.Instance.Equals(this, other); + } + public override bool Equals(object obj) { return FieldDefinitionComparer.Instance.Equals(this, obj as IFieldDefinition); diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index d457aeeb..7556afd8 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -142,7 +142,7 @@ public class when_the_issue_factory_parses_an_issue_with_links : WorkItemMapperC { public override void Given() { - var wit = new MockWorkItemType("Baz", WorkItemBackingStore.Select(s => new MockFieldDefinition(s.Key, null))); + var wit = new MockWorkItemType("Baz", WorkItemBackingStore.Select(s => new MockFieldDefinition(s.Key))); WorkItemStore = new MockWorkItemStore( diff --git a/test/Qwiq.Mocks/MockFieldDefinition.cs b/test/Qwiq.Mocks/MockFieldDefinition.cs index 15030046..9092c1cb 100644 --- a/test/Qwiq.Mocks/MockFieldDefinition.cs +++ b/test/Qwiq.Mocks/MockFieldDefinition.cs @@ -1,21 +1,25 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Qwiq.Mocks { - public class MockFieldDefinition : IFieldDefinition + public class MockFieldDefinition : Microsoft.Qwiq.Proxies.FieldDefinitionProxy { + public MockFieldDefinition(string referenceName) + { + if (string.IsNullOrWhiteSpace(referenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); + + ReferenceName = referenceName; + } + public MockFieldDefinition(string name, string referenceName) { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + if (string.IsNullOrWhiteSpace(referenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); Name = name; ReferenceName = referenceName; } - - public string Name { get; } - - public string ReferenceName { get; } } } diff --git a/test/Qwiq.Mocks/MockWorkItemType.cs b/test/Qwiq.Mocks/MockWorkItemType.cs index ca88d78e..343eb0e2 100644 --- a/test/Qwiq.Mocks/MockWorkItemType.cs +++ b/test/Qwiq.Mocks/MockWorkItemType.cs @@ -16,7 +16,7 @@ public MockWorkItemType() [Obsolete( "This method has been deprecated and will be removed in a future release. See ctor(String, IEnumerable, String).")] public MockWorkItemType(string name) - : this(name, CoreFieldRefNames.All.Select(s => new MockFieldDefinition(null, s))) + : this(name, CoreFieldRefNames.All.Select(s => new MockFieldDefinition(s))) { } From c788a399166f09c8195d2cf039b4c1f568e11c99 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 13:37:46 -0700 Subject: [PATCH 061/251] Convert `MockNode` to Core proxy --- src/Qwiq.Core/Proxies/NodeProxy.cs | 2 +- test/Qwiq.Mocks/MockNode.cs | 23 ++--------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src/Qwiq.Core/Proxies/NodeProxy.cs b/src/Qwiq.Core/Proxies/NodeProxy.cs index d94a0557..2b052c1a 100644 --- a/src/Qwiq.Core/Proxies/NodeProxy.cs +++ b/src/Qwiq.Core/Proxies/NodeProxy.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq.Proxies { - internal class NodeProxy : INode, IComparer, IEquatable + public class NodeProxy : INode, IComparer, IEquatable { public IEnumerable ChildNodes { get; internal set; } diff --git a/test/Qwiq.Mocks/MockNode.cs b/test/Qwiq.Mocks/MockNode.cs index f5643a51..16b8740d 100644 --- a/test/Qwiq.Mocks/MockNode.cs +++ b/test/Qwiq.Mocks/MockNode.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; using System.Linq; namespace Microsoft.Qwiq.Mocks { - public class MockNode : INode + public class MockNode : Microsoft.Qwiq.Proxies.NodeProxy { public MockNode(string name, bool isArea, bool isIteration) { @@ -13,24 +11,7 @@ public MockNode(string name, bool isArea, bool isIteration) IsIterationNode = isIteration; ChildNodes = Enumerable.Empty(); ParentNode = null; + Path = ((ParentNode?.Path ?? string.Empty) + "\\" + Name).Trim('\\'); } - - public IEnumerable ChildNodes { get; set; } - - public bool HasChildNodes => ChildNodes.Any(); - - public int Id { get; } - - public bool IsAreaNode { get; } - - public bool IsIterationNode { get; } - - public string Name { get; } - - public INode ParentNode { get; set; } - - public string Path => ParentNode?.Path + "\\" + Name; - - public Uri Uri { get; } } } From d8d90bab3f5bf77b0b141c16816248f79b9eb157 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 13:57:44 -0700 Subject: [PATCH 062/251] Convert `MockProject` to Core proxy --- src/Qwiq.Core/Proxies/ProjectProxy.cs | 2 +- test/Qwiq.Mocks/MockProject.cs | 65 ++++++++++----------------- 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/src/Qwiq.Core/Proxies/ProjectProxy.cs b/src/Qwiq.Core/Proxies/ProjectProxy.cs index 074bf929..29d58ec0 100644 --- a/src/Qwiq.Core/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core/Proxies/ProjectProxy.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq.Proxies { - internal class ProjectProxy : IProject, IComparer, IEquatable + public class ProjectProxy : IProject, IComparer, IEquatable { private readonly Lazy> _area; diff --git a/test/Qwiq.Mocks/MockProject.cs b/test/Qwiq.Mocks/MockProject.cs index fa564085..983b94f8 100644 --- a/test/Qwiq.Mocks/MockProject.cs +++ b/test/Qwiq.Mocks/MockProject.cs @@ -1,59 +1,40 @@ using System; using System.Collections.Generic; +using Microsoft.Qwiq.Proxies; + namespace Microsoft.Qwiq.Mocks { - public class MockProject : IProject + public class MockProject : ProjectProxy { - - - public MockProject(IWorkItemStore store, INode node) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - Store = store ?? throw new ArgumentNullException(nameof(store)); - - WorkItemTypes = new[] - { - new MockWorkItemType("Task"), - new MockWorkItemType("Deliverable"), - new MockWorkItemType("Scenario"), - new MockWorkItemType("Customer Promise"), - new MockWorkItemType("Bug"), - new MockWorkItemType("Measure") - }; - - AreaRootNodes = new[] { node }; - IterationRootNodes = new[] { CreateNodes(false) }; - Guid = Guid.NewGuid(); - } - public MockProject(IWorkItemStore store) - :this(store, CreateNodes(true)) + : base( + BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0), + Guid.NewGuid(), + "Mock", + new Uri("http://localhost"), + store, + new Lazy>( + () => new[] + { + new MockWorkItemType("Task"), + new MockWorkItemType("Deliverable"), + new MockWorkItemType("Scenario"), + new MockWorkItemType("Customer Promise"), + new MockWorkItemType("Bug"), + new MockWorkItemType("Measure") + }), + new Lazy>(() => new[] { CreateNodes(true) }), + new Lazy>(() => new[] { CreateNodes(false) })) { } [Obsolete("This method has been deprecated and will be removed in a future release. See ctor(IWorkItemStore).")] public MockProject() - : this(new MockWorkItemStore(), CreateNodes(true)) + : this(new MockWorkItemStore()) { } - public IEnumerable AreaRootNodes { get; set; } - - public Guid Guid { get; set; } - - public int Id { get; set; } - - public IEnumerable IterationRootNodes { get; set; } - - public string Name => "Mock"; - - public Uri Uri { get; set; } - - public IEnumerable WorkItemTypes { get; set; } - - public IWorkItemStore Store { get; } - private static MockNode CreateNodes(bool area) { var root = new MockNode("Root", true, !area); @@ -65,4 +46,4 @@ private static MockNode CreateNodes(bool area) return root; } } -} +} \ No newline at end of file From decb0be6165f9b79a927f9ca1224171883179f61 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 14:08:54 -0700 Subject: [PATCH 063/251] Convert `MockFieldDefinitionCollection` to Core proxy --- .../Proxies/FieldDefinitionCollectionProxy.cs | 32 ++++++------- .../MockFieldDefinitionCollection.cs | 48 ++++++++----------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs b/src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs index 5581011a..b376e6e8 100644 --- a/src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs +++ b/src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs @@ -4,39 +4,37 @@ namespace Microsoft.Qwiq.Proxies { - internal abstract class FieldDefinitionCollectionProxy : IFieldDefinitionCollection + public abstract class FieldDefinitionCollectionProxy : IFieldDefinitionCollection { - public abstract IEnumerator GetEnumerator(); - - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - public abstract int Count { get; } public abstract IFieldDefinition this[string name] { get; } - public abstract bool Contains(string fieldName); - public override int GetHashCode() + public abstract IEnumerator GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() { - unchecked - { - return this.Aggregate(27, (current, f) => (13 * current) ^ f.GetHashCode()); - } + return GetEnumerator(); } public override bool Equals(object obj) { - if(ReferenceEquals(this, obj)) return true; + if (ReferenceEquals(this, obj)) return true; if (ReferenceEquals(obj, null)) return false; var fdc = obj as IFieldDefinitionCollection; if (fdc == null) return false; return this.All(p => fdc.Contains(p, FieldDefinitionComparer.Instance)); } + + public override int GetHashCode() + { + unchecked + { + return this.Aggregate(27, (current, f) => (13 * current) ^ f.GetHashCode()); + } + } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs index b179913b..3bb609c2 100644 --- a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs +++ b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; @@ -7,57 +6,50 @@ namespace Microsoft.Qwiq.Mocks { - public class MockFieldDefinitionCollection : IFieldDefinitionCollection + public class MockFieldDefinitionCollection : Microsoft.Qwiq.Proxies.FieldDefinitionCollectionProxy { private readonly Dictionary _fieldUsagesByName; private readonly Dictionary _fieldUsagesByReferenceName; - public MockFieldDefinitionCollection(IEnumerable fieldDefintions) + public MockFieldDefinitionCollection(IEnumerable fieldDefinitions) { - if (fieldDefintions == null) throw new ArgumentNullException(nameof(fieldDefintions)); + if (fieldDefinitions == null) throw new ArgumentNullException(nameof(fieldDefinitions)); - - _fieldUsagesByName = fieldDefintions.Where(p => !string.IsNullOrEmpty(p.Name)).ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); - _fieldUsagesByReferenceName = fieldDefintions.Where(p => !string.IsNullOrEmpty(p.ReferenceName)).ToDictionary(k => k.ReferenceName, e => e, StringComparer.OrdinalIgnoreCase); - } - - - - public IEnumerator GetEnumerator() - { - return _fieldUsagesByName.Values.GetEnumerator(); + _fieldUsagesByName = fieldDefinitions.Where(p => !string.IsNullOrWhiteSpace(p.Name)).ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); + _fieldUsagesByReferenceName = fieldDefinitions.Where(p => !string.IsNullOrWhiteSpace(p.ReferenceName)).ToDictionary( + k => k.ReferenceName, + e => e, + StringComparer.OrdinalIgnoreCase); } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public override int Count => _fieldUsagesByName.Count; - public int Count => _fieldUsagesByName.Count; - - public IFieldDefinition this[string name] + public override IFieldDefinition this[string name] { get { - if (string.IsNullOrEmpty(name)) throw new ArgumentException("Value cannot be null or empty.", nameof(name)); + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Value cannot be null or empty.", nameof(name)); if (!_fieldUsagesByName.TryGetValue(name, out IFieldDefinition def)) - { if (!_fieldUsagesByReferenceName.TryGetValue(name, out def)) - { throw new FieldDefinitionNotExistException(name); - } - } return def; } } - public bool Contains(string fieldName) + public override bool Contains(string fieldName) { - if (string.IsNullOrEmpty(fieldName)) throw new ArgumentException("Value cannot be null or empty.", nameof(fieldName)); + if (string.IsNullOrEmpty(fieldName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(fieldName)); return _fieldUsagesByName.ContainsKey(fieldName) || _fieldUsagesByReferenceName.ContainsKey(fieldName); } + + public override IEnumerator GetEnumerator() + { + return _fieldUsagesByName.Values.GetEnumerator(); + } } } \ No newline at end of file From f36e0df3454b04a4fee46bf666855b3016d2236b Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 14:18:15 -0700 Subject: [PATCH 064/251] Convert `MockWorkItemType` to Core proxy --- src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs | 35 ++++++++++++---------- test/Qwiq.Mocks/MockWorkItemType.cs | 18 ++--------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs index 76d59b9a..a3ab31a8 100644 --- a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs @@ -2,11 +2,14 @@ namespace Microsoft.Qwiq.Proxies { - public class WorkItemTypeProxy : IWorkItemType, IEquatable + public class WorkItemTypeProxy : IWorkItemType, IEquatable, IComparable { private readonly Lazy _fieldDefinitions; - private readonly Func _workItemFactory; + internal WorkItemTypeProxy(string name, string description, Lazy fieldDefinitions) + : this(name, description, fieldDefinitions, null) + { + } internal WorkItemTypeProxy( string name, @@ -14,30 +17,28 @@ internal WorkItemTypeProxy( Lazy fieldDefinitions, Func workItemFactory) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - - _fieldDefinitions = fieldDefinitions; - _workItemFactory = workItemFactory; + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + _fieldDefinitions = fieldDefinitions ?? throw new ArgumentNullException(nameof(fieldDefinitions)); + WorkItemFactory = workItemFactory; Name = name; Description = description; } - public bool Equals(IWorkItemType other) - { - return WorkItemTypeComparer.Instance.Equals(this, other); - } - public string Description { get; } - public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions.Value; - public string Name { get; } + protected internal Func WorkItemFactory { get; internal set; } - public IWorkItem NewWorkItem() + public int CompareTo(IWorkItemType other) { - return _workItemFactory(); + return WorkItemTypeComparer.Instance.Compare(this, other); } + public bool Equals(IWorkItemType other) + { + return WorkItemTypeComparer.Instance.Equals(this, other); + } public override bool Equals(object obj) { return WorkItemTypeComparer.Instance.Equals(this, obj as IWorkItemType); @@ -48,6 +49,10 @@ public override int GetHashCode() return WorkItemTypeComparer.Instance.GetHashCode(this); } + public IWorkItem NewWorkItem() + { + return WorkItemFactory(); + } public override string ToString() { return Name; diff --git a/test/Qwiq.Mocks/MockWorkItemType.cs b/test/Qwiq.Mocks/MockWorkItemType.cs index 343eb0e2..e81c84bc 100644 --- a/test/Qwiq.Mocks/MockWorkItemType.cs +++ b/test/Qwiq.Mocks/MockWorkItemType.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemType : IWorkItemType + public class MockWorkItemType : Microsoft.Qwiq.Proxies.WorkItemTypeProxy { [Obsolete( "This method has been deprecated and will be removed in a future release. See ctor(IWorkItemStore, String, String).")] @@ -27,21 +27,9 @@ public MockWorkItemType(string name, IEnumerable fieldDefiniti } public MockWorkItemType(string name, IFieldDefinitionCollection fieldDefinitions, string description = null) + : base(name, description, new Lazy(() => fieldDefinitions)) { - Name = name ?? throw new ArgumentNullException(nameof(name)); - FieldDefinitions = fieldDefinitions ?? throw new ArgumentNullException(nameof(fieldDefinitions)); - Description = description; - } - - public string Description { get; } - - public IFieldDefinitionCollection FieldDefinitions { get; } - - public string Name { get; } - - public IWorkItem NewWorkItem() - { - return new MockWorkItem(this); + WorkItemFactory = () => new MockWorkItem(this); } } } \ No newline at end of file From d900b8fc880cd33532971ffc64590bc62898d5a4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 14:34:53 -0700 Subject: [PATCH 065/251] Convert `MockWorkItemLinkInfo` to Core proxy --- .../Proxies/WorkItemLinkInfoProxy.cs | 13 ++++- test/Qwiq.Core.Tests/LinkTypeTests.cs | 24 ++++----- test/Qwiq.Mocks/MockWorkItemLinkInfo.cs | 33 +++--------- .../ParentIdMapperStrategyTests.cs | 54 +++++-------------- ...ndationServerWorkItemQueryProviderTests.cs | 30 ++--------- 5 files changed, 46 insertions(+), 108 deletions(-) diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs index 6dc2d7a1..27087f39 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs +++ b/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs @@ -2,10 +2,16 @@ namespace Microsoft.Qwiq.Proxies { - internal class WorkItemLinkInfoProxy : IWorkItemLinkInfo, IEquatable + public class WorkItemLinkInfoProxy : IWorkItemLinkInfo, IEquatable, IComparable { private readonly Lazy _id; + internal WorkItemLinkInfoProxy(int id) + :this(new Lazy(()=>id)) + { + + } + internal WorkItemLinkInfoProxy(Lazy id) { _id = id; @@ -44,6 +50,11 @@ public override int GetHashCode() return WorkItemLinkInfoComparer.Instance.GetHashCode(this); } + public int CompareTo(IWorkItemLinkInfo other) + { + return WorkItemLinkInfoComparer.Instance.Compare(this, other); + } + public override string ToString() { return $"S:{SourceId} T:{TargetId} ID:{LinkTypeId}"; diff --git a/test/Qwiq.Core.Tests/LinkTypeTests.cs b/test/Qwiq.Core.Tests/LinkTypeTests.cs index cf43ee0a..856602de 100644 --- a/test/Qwiq.Core.Tests/LinkTypeTests.cs +++ b/test/Qwiq.Core.Tests/LinkTypeTests.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -12,25 +10,23 @@ namespace Microsoft.Qwiq.Core.Tests [TestClass] public class LinkTypeTests : WorkItemStoreComparisonContextSpecification { - - - private IEnumerable _soapResult; - private IEnumerable _restResult; - - - public override void When() - { - _soapResult = Soap.WorkItemLinkTypes.ToList(); - _restResult = Rest.WorkItemLinkTypes.ToList(); - } + private IEnumerable _soapResult; [TestMethod] [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] public void Equal() { _restResult.ShouldContainOnly(_soapResult); } + + public override void When() + { + _soapResult = Soap.WorkItemLinkTypes.ToList(); + _restResult = Rest.WorkItemLinkTypes.ToList(); + } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs b/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs index 3cbc5cd6..0c02b794 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs @@ -1,41 +1,22 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkInfo : IWorkItemLinkInfo + public class MockWorkItemLinkInfo : Microsoft.Qwiq.Proxies.WorkItemLinkInfoProxy { public MockWorkItemLinkInfo() + : base(0) { } public MockWorkItemLinkInfo(int sourceId, int targetId) + : this(sourceId, targetId, 0) { - SourceId = sourceId; - TargetId = targetId; - } - - public bool IsLocked { get; set; } - public int LinkTypeId { get; set; } - public int SourceId { get; set; } - public int TargetId { get; set; } - - public static bool operator !=(MockWorkItemLinkInfo x, MockWorkItemLinkInfo y) - { - return !WorkItemLinkInfoComparer.Instance.Equals(x, y); - } - - public static bool operator ==(MockWorkItemLinkInfo x, MockWorkItemLinkInfo y) - { - return WorkItemLinkInfoComparer.Instance.Equals(x, y); } - public override bool Equals(object obj) + public MockWorkItemLinkInfo(int sourceId, int targetId, int linkTypeId) + : base(linkTypeId) { - if (!(obj is IWorkItemLinkInfo)) return false; - return WorkItemLinkInfoComparer.Instance.Equals(this, (IWorkItemLinkInfo)obj); - } - - public override int GetHashCode() - { - return WorkItemLinkInfoComparer.Instance.GetHashCode(this); + SourceId = sourceId; + TargetId = targetId; } } } diff --git a/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs b/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs index 1a634217..fa3e1fac 100644 --- a/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs +++ b/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs @@ -40,12 +40,7 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo - { - TargetId = 1, - SourceId = 2, - LinkTypeId = -2 - } + new MockWorkItemLinkInfo(2,1,-2) }; var workItems = new[] @@ -146,12 +141,7 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo - { - TargetId = 1, - SourceId = 2, - LinkTypeId = -2 - } + new MockWorkItemLinkInfo(2,1,-2) }; var workItems = new[] @@ -204,18 +194,8 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo - { - TargetId = 1, - SourceId = 2, - LinkTypeId = -2 - }, - new MockWorkItemLinkInfo - { - TargetId = 3, - SourceId = 4, - LinkTypeId = 0 - } + new MockWorkItemLinkInfo(2,1,-2), + new MockWorkItemLinkInfo(4,3,0) }; var workItems = new[] @@ -268,33 +248,23 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo - { - TargetId = 1, - SourceId = 2, - LinkTypeId = -2 - }, - new MockWorkItemLinkInfo - { - TargetId = 3, - SourceId = 4, - LinkTypeId = 0 - } + new MockWorkItemLinkInfo(2,1,-2), + new MockWorkItemLinkInfo(4,3,0) }; + var wit = new MockWorkItemType("SimpleMockWorkItem"); + var workItems = new[] { - new MockWorkItem + new MockWorkItem(wit) { Id = 2, - ChangedDate = new DateTime(2010, 10, 10), - Type = new MockWorkItemType("SimpleMockWorkItem") + ChangedDate = new DateTime(2010, 10, 10) }, - new MockWorkItem + new MockWorkItem(wit) { Id = 4, - ChangedDate = new DateTime(2011, 11, 11), - Type = new MockWorkItemType("SimpleMockWorkItem") + ChangedDate = new DateTime(2011, 11, 11) } }; diff --git a/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs b/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs index dc118683..acd1495a 100644 --- a/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs +++ b/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs @@ -78,31 +78,11 @@ public override void Given() }; WorkItemStoreWorkItemLinks = new[] { - new MockWorkItemLinkInfo - { - SourceId = 0, - TargetId = 3 - }, - new MockWorkItemLinkInfo - { - SourceId = 3, - TargetId = 1 - }, - new MockWorkItemLinkInfo - { - SourceId = 3, - TargetId = 2 - }, - new MockWorkItemLinkInfo - { - SourceId = 0, - TargetId = 4 - }, - new MockWorkItemLinkInfo - { - SourceId = 0, - TargetId = 5 - } + new MockWorkItemLinkInfo(0, 3), + new MockWorkItemLinkInfo(3, 1), + new MockWorkItemLinkInfo(3, 2), + new MockWorkItemLinkInfo(0, 4), + new MockWorkItemLinkInfo(0, 5) }; base.Given(); From 8952e118be1a68012bead291ef4087f9949a7cff Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 16:08:51 -0700 Subject: [PATCH 066/251] Update "Proxy" objects The type is an adapter--projecting a new interface and simplifying. Moving type from the Proxy namespace to the root and dropping the "Proxy" suffix. --- ...dDefinitionProxy.cs => FieldDefinition.cs} | 7 +- ...nProxy.cs => FieldDefinitionCollection.cs} | 10 +- .../Proxies/{ProjectProxy.cs => Project.cs} | 10 +- src/Qwiq.Core.Rest/Proxies/QueryProxy.cs | 2 +- ...ectionProxy.cs => VssConnectionAdapter.cs} | 4 +- .../WorkItemClassificationNodeProxy.cs | 6 +- .../Proxies/WorkItemLinkInfo.cs | 16 +++ .../Proxies/WorkItemLinkInfoProxy.cs | 17 --- ...emLinkTypeProxy.cs => WorkItemLinkType.cs} | 10 +- .../Proxies/WorkItemLinkTypeEnd.cs | 16 +++ .../Proxies/WorkItemLinkTypeEndProxy.cs | 13 -- .../Proxies/WorkItemStoreProxy.cs | 14 +- .../{WorkItemTypeProxy.cs => WorkItemType.cs} | 6 +- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 16 +-- src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 2 +- src/Qwiq.Core.Soap/LinkTypeEndMapper.cs | 2 +- src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs | 15 +++ ...nProxy.cs => FieldDefinitionCollection.cs} | 12 +- .../Proxies/FieldDefinitionProxy.cs | 16 --- .../Proxies/{NodeProxy.cs => Node.cs} | 8 +- .../Proxies/{ProjectProxy.cs => Project.cs} | 10 +- src/Qwiq.Core.Soap/Proxies/QueryProxy.cs | 4 +- .../Proxies/RelatedLinkProxy.cs | 2 +- .../Proxies/WorkItemLinkInfo.cs | 15 +++ .../Proxies/WorkItemLinkInfoProxy.cs | 17 --- ...emLinkTypeProxy.cs => WorkItemLinkType.cs} | 11 +- .../Proxies/WorkItemLinkTypeEnd.cs | 19 +++ .../Proxies/WorkItemLinkTypeEndProxy.cs | 19 --- src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs | 2 +- .../Proxies/WorkItemStoreProxy.cs | 8 +- .../{WorkItemTypeProxy.cs => WorkItemType.cs} | 6 +- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 16 +-- src/Qwiq.Core/CoreFieldRefNames.cs | 121 ++++++++++++++++++ src/Qwiq.Core/CoreLinkTypeReferenceNames.cs | 113 ++-------------- src/Qwiq.Core/CoreLinkTypes.cs | 18 +++ src/Qwiq.Core/FieldDefinition.cs | 53 ++++++++ ...nProxy.cs => FieldDefinitionCollection.cs} | 9 +- src/Qwiq.Core/IFieldDefinition.cs | 8 +- .../{Proxies/NodeProxy.cs => Node.cs} | 7 +- .../{Proxies/ProjectProxy.cs => Project.cs} | 8 +- src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs | 31 ----- src/Qwiq.Core/Qwiq.Core.csproj | 26 ++-- ...emLinkInfoProxy.cs => WorkItemLinkInfo.cs} | 20 +-- ...emLinkTypeProxy.cs => WorkItemLinkType.cs} | 17 ++- .../WorkItemLinkTypeCollection.cs | 10 +- ...TypeEndProxy.cs => WorkItemLinkTypeEnd.cs} | 27 +++- .../WorkItemLinkTypeEndCollection.cs | 4 +- .../WorkItemTypeProxy.cs => WorkItemType.cs} | 8 +- .../PerformanceTests.cs | 15 ++- .../Mocks/InstrumentedMockWorkItemStore.cs | 2 +- test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 2 +- test/Qwiq.Mocks/MockFieldDefinition.cs | 17 +-- .../MockFieldDefinitionCollection.cs | 2 +- test/Qwiq.Mocks/MockNode.cs | 2 +- test/Qwiq.Mocks/MockProject.cs | 2 +- test/Qwiq.Mocks/MockWorkItemLinkInfo.cs | 11 +- test/Qwiq.Mocks/MockWorkItemLinkType.cs | 10 +- test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs | 25 ++-- test/Qwiq.Mocks/MockWorkItemStore.cs | 4 +- test/Qwiq.Mocks/MockWorkItemType.cs | 8 +- 60 files changed, 493 insertions(+), 418 deletions(-) rename src/Qwiq.Core.Rest/Proxies/{FieldDefinitionProxy.cs => FieldDefinition.cs} (50%) rename src/Qwiq.Core.Rest/Proxies/{FieldDefinitionCollectionProxy.cs => FieldDefinitionCollection.cs} (83%) rename src/Qwiq.Core.Rest/Proxies/{ProjectProxy.cs => Project.cs} (85%) rename src/Qwiq.Core.Rest/Proxies/{VssConnectionProxy.cs => VssConnectionAdapter.cs} (91%) create mode 100644 src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs delete mode 100644 src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfoProxy.cs rename src/Qwiq.Core.Rest/Proxies/{WorkItemLinkTypeProxy.cs => WorkItemLinkType.cs} (53%) create mode 100644 src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs delete mode 100644 src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEndProxy.cs rename src/Qwiq.Core.Rest/Proxies/{WorkItemTypeProxy.cs => WorkItemType.cs} (88%) create mode 100644 src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs rename src/Qwiq.Core.Soap/Proxies/{FieldDefinitionCollectionProxy.cs => FieldDefinitionCollection.cs} (60%) delete mode 100644 src/Qwiq.Core.Soap/Proxies/FieldDefinitionProxy.cs rename src/Qwiq.Core.Soap/Proxies/{NodeProxy.cs => Node.cs} (67%) rename src/Qwiq.Core.Soap/Proxies/{ProjectProxy.cs => Project.cs} (77%) create mode 100644 src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfoProxy.cs rename src/Qwiq.Core.Soap/Proxies/{WorkItemLinkTypeProxy.cs => WorkItemLinkType.cs} (54%) create mode 100644 src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEndProxy.cs rename src/Qwiq.Core.Soap/Proxies/{WorkItemTypeProxy.cs => WorkItemType.cs} (75%) create mode 100644 src/Qwiq.Core/CoreFieldRefNames.cs create mode 100644 src/Qwiq.Core/CoreLinkTypes.cs create mode 100644 src/Qwiq.Core/FieldDefinition.cs rename src/Qwiq.Core/{Proxies/FieldDefinitionCollectionProxy.cs => FieldDefinitionCollection.cs} (78%) rename src/Qwiq.Core/{Proxies/NodeProxy.cs => Node.cs} (89%) rename src/Qwiq.Core/{Proxies/ProjectProxy.cs => Project.cs} (92%) delete mode 100644 src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs rename src/Qwiq.Core/{Proxies/WorkItemLinkInfoProxy.cs => WorkItemLinkInfo.cs} (63%) rename src/Qwiq.Core/{Proxies/WorkItemLinkTypeProxy.cs => WorkItemLinkType.cs} (68%) rename src/Qwiq.Core/{Proxies => }/WorkItemLinkTypeCollection.cs (81%) rename src/Qwiq.Core/{Proxies/WorkItemLinkTypeEndProxy.cs => WorkItemLinkTypeEnd.cs} (50%) rename src/Qwiq.Core/{Proxies => }/WorkItemLinkTypeEndCollection.cs (91%) rename src/Qwiq.Core/{Proxies/WorkItemTypeProxy.cs => WorkItemType.cs} (85%) diff --git a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionProxy.cs b/src/Qwiq.Core.Rest/Proxies/FieldDefinition.cs similarity index 50% rename from src/Qwiq.Core.Rest/Proxies/FieldDefinitionProxy.cs rename to src/Qwiq.Core.Rest/Proxies/FieldDefinition.cs index 6c28520e..86bc07c1 100644 --- a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/FieldDefinition.cs @@ -4,13 +4,12 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal class FieldDefinitionProxy : Microsoft.Qwiq.Proxies.FieldDefinitionProxy + internal class FieldDefinition : Qwiq.FieldDefinition { - internal FieldDefinitionProxy(WorkItemFieldReference field) + internal FieldDefinition(WorkItemFieldReference field) + :base(field?.ReferenceName, field?.Name) { if (field == null) throw new ArgumentNullException(nameof(field)); - Name = field.Name; - ReferenceName = field.ReferenceName; } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollectionProxy.cs b/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs similarity index 83% rename from src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollectionProxy.cs rename to src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs index 858673a2..a688f3b9 100644 --- a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollectionProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs @@ -7,23 +7,23 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal class FieldDefinitionCollectionProxy : Microsoft.Qwiq.Proxies.FieldDefinitionCollectionProxy + internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { private readonly Dictionary _fieldUsagesByName; private readonly Dictionary _fieldUsagesByReferenceName; - internal FieldDefinitionCollectionProxy(IEnumerable typeFields) + internal FieldDefinitionCollection(IEnumerable typeFields) : this(typeFields.Where(p => p != null).Select(s => s.Field)) { } - internal FieldDefinitionCollectionProxy(IEnumerable typeFields) - : this(typeFields.Where(p=>p != null).Select(s => new FieldDefinitionProxy(s))) + internal FieldDefinitionCollection(IEnumerable typeFields) + : this(typeFields.Where(p=>p != null).Select(s => new FieldDefinition(s))) { } - private FieldDefinitionCollectionProxy(IEnumerable fieldDefinitions) + private FieldDefinitionCollection(IEnumerable fieldDefinitions) { if (fieldDefinitions == null) throw new ArgumentNullException(nameof(fieldDefinitions)); diff --git a/src/Qwiq.Core.Rest/Proxies/ProjectProxy.cs b/src/Qwiq.Core.Rest/Proxies/Project.cs similarity index 85% rename from src/Qwiq.Core.Rest/Proxies/ProjectProxy.cs rename to src/Qwiq.Core.Rest/Proxies/Project.cs index 8d0fc935..56a18c0b 100644 --- a/src/Qwiq.Core.Rest/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/Project.cs @@ -7,9 +7,9 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal class ProjectProxy : Microsoft.Qwiq.Proxies.ProjectProxy + internal class Project : Qwiq.Project { - internal ProjectProxy(TeamProjectReference project, WorkItemStoreProxy store) + internal Project(TeamProjectReference project, WorkItemStoreProxy store) : base( // REST API stores ID as GUID rather than INT // Converting from 128-bit GUID will have some loss in precision @@ -22,7 +22,7 @@ internal ProjectProxy(TeamProjectReference project, WorkItemStoreProxy store) () => { var wits = store.NativeWorkItemStore.Value.GetWorkItemTypesAsync(project.Name).GetAwaiter().GetResult(); - return wits.Select(s => new WorkItemTypeProxy(s)); + return wits.Select(s => new WorkItemType(s)); }), new Lazy>( () => @@ -32,7 +32,7 @@ internal ProjectProxy(TeamProjectReference project, WorkItemStoreProxy store) .GetAwaiter() .GetResult(); - return new[] { new NodeProxy(result), }; + return new[] { new Node(result), }; }), new Lazy>( () => @@ -42,7 +42,7 @@ internal ProjectProxy(TeamProjectReference project, WorkItemStoreProxy store) .GetAwaiter() .GetResult(); - return new[] { new NodeProxy(result) }; + return new[] { new Node(result) }; }) ) { diff --git a/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs b/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs index 89df3cc0..4541b8b6 100644 --- a/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs @@ -88,7 +88,7 @@ public IEnumerable RunLinkQuery() foreach (var workItemLink in result.WorkItemRelations) { - yield return new WorkItemLinkInfoProxy( + yield return new WorkItemLinkInfo( workItemLink, new Lazy(() => ends.Value.TryGetByName(workItemLink.Rel, out IWorkItemLinkTypeEnd end) ? end.Id : 0)); } diff --git a/src/Qwiq.Core.Rest/Proxies/VssConnectionProxy.cs b/src/Qwiq.Core.Rest/Proxies/VssConnectionAdapter.cs similarity index 91% rename from src/Qwiq.Core.Rest/Proxies/VssConnectionProxy.cs rename to src/Qwiq.Core.Rest/Proxies/VssConnectionAdapter.cs index cd0b44d0..3e84d5c1 100644 --- a/src/Qwiq.Core.Rest/Proxies/VssConnectionProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/VssConnectionAdapter.cs @@ -5,11 +5,11 @@ namespace Microsoft.Qwiq.Rest.Proxies { - public class VssConnectionProxy : IInternalTfsTeamProjectCollection + internal class VssConnectionAdapter : IInternalTfsTeamProjectCollection { private readonly VssConnection _connection; - public VssConnectionProxy(VssConnection connection) + public VssConnectionAdapter(VssConnection connection) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); AuthorizedCredentials = new TfsCredentials(connection.Credentials); diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs index a1ac770f..33bc539a 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs @@ -5,9 +5,9 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal class NodeProxy : Microsoft.Qwiq.Proxies.NodeProxy + internal class Node : Qwiq.Node { - internal NodeProxy(WorkItemClassificationNode node) + internal Node(WorkItemClassificationNode node) { if (node == null) throw new ArgumentNullException(nameof(node)); @@ -17,7 +17,7 @@ internal NodeProxy(WorkItemClassificationNode node) Name = node.Name; HasChildNodes = node.Children?.Any() ?? false; ChildNodes = HasChildNodes - ? node.Children.Select(s => new NodeProxy(s) { ParentNode = this }).ToList() + ? node.Children.Select(s => new Node(s) { ParentNode = this }).ToList() : Enumerable.Empty(); Path = ((ParentNode?.Path ?? string.Empty) + "\\" + Name).Trim('\\'); diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs new file mode 100644 index 00000000..a50c5f95 --- /dev/null +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs @@ -0,0 +1,16 @@ +using System; + +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; + +namespace Microsoft.Qwiq.Rest.Proxies +{ + internal class WorkItemLinkInfo : Qwiq.WorkItemLinkInfo + { + internal WorkItemLinkInfo(WorkItemLink item, Lazy id) + : base(item.Source?.Id ?? 0, item.Target?.Id ?? 0, id) + { + if (item == null) throw new ArgumentNullException(nameof(item)); + IsLocked = false; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfoProxy.cs deleted file mode 100644 index 175cb754..00000000 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfoProxy.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - -namespace Microsoft.Qwiq.Rest.Proxies -{ - internal class WorkItemLinkInfoProxy : Microsoft.Qwiq.Proxies.WorkItemLinkInfoProxy - { - internal WorkItemLinkInfoProxy(WorkItemLink item, Lazy id) - : base(id) - { - IsLocked = false; - SourceId = item.Source?.Id ?? 0; - TargetId = item.Target?.Id ?? 0; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs similarity index 53% rename from src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeProxy.cs rename to src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs index fba353b1..099f7850 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs @@ -2,15 +2,11 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal partial class WorkItemLinkTypeProxy : Microsoft.Qwiq.Proxies.WorkItemLinkTypeProxy + internal partial class WorkItemLinkType : Qwiq.WorkItemLinkType { - internal WorkItemLinkTypeProxy( - string referenceName - ) + internal WorkItemLinkType(string referenceName) + : base(referenceName) { - if (string.IsNullOrEmpty(referenceName)) - throw new ArgumentException("Value cannot be null or empty.", nameof(referenceName)); - ReferenceName = referenceName; } internal void SetForwardEnd(IWorkItemLinkTypeEnd forward) diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs new file mode 100644 index 00000000..b693046e --- /dev/null +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs @@ -0,0 +1,16 @@ +using System; + +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; + +namespace Microsoft.Qwiq.Rest.Proxies +{ + internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd + { + internal WorkItemLinkTypeEnd(WorkItemRelationType item) + : base(item?.ReferenceName) + { + if (item == null) throw new ArgumentNullException(nameof(item)); + Name = item.Name; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEndProxy.cs deleted file mode 100644 index 168930e9..00000000 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEndProxy.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - -namespace Microsoft.Qwiq.Rest.Proxies -{ - internal partial class WorkItemLinkTypeEndProxy : Microsoft.Qwiq.Proxies.WorkItemLinkTypeEndProxy - { - internal WorkItemLinkTypeEndProxy(WorkItemRelationType item) - { - ImmutableName = item.ReferenceName; - Name = item.Name; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs index 1ed250e9..66581aee 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs @@ -71,7 +71,7 @@ WorkItemLinkTypeCollection ValueFactory() using (var projectHttpClient = _tfs.Value.GetClient()) { var projects = projectHttpClient.GetProjects(ProjectState.All).GetAwaiter().GetResult(); - return projects.Select(project => new ProjectProxy(project, this)) + return projects.Select(project => new Project(project, this)) .Cast() .ToList(); } @@ -140,7 +140,7 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo { var types = workItemStore.GetRelationTypesAsync().GetAwaiter().GetResult(); var d = new Dictionary>(StringComparer.OrdinalIgnoreCase); - var d2 = new Dictionary(StringComparer.OrdinalIgnoreCase); + var d2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var type in types.Where(p => (string)p.Attributes["usage"] == "workItemLink")) { @@ -152,7 +152,7 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo if (!d.ContainsKey(linkRef)) d[linkRef] = new List(); d[linkRef].Add(type); - if (!d2.ContainsKey(linkRef)) d2[linkRef] = new WorkItemLinkTypeProxy(linkRef); + if (!d2.ContainsKey(linkRef)) d2[linkRef] = new WorkItemLinkType(linkRef); } foreach (var kvp in d2) @@ -169,9 +169,9 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo if (!forwardEnd.ReferenceName.EndsWith("Forward")) forwardEnd.ReferenceName += "-Forward"; - type.SetForwardEnd(new WorkItemLinkTypeEndProxy(forwardEnd) { IsForwardLink = true, LinkType = type }); + type.SetForwardEnd(new WorkItemLinkTypeEnd(forwardEnd) { IsForwardLink = true, LinkType = type }); type.SetReverseEnd(type.IsDirectional - ? new WorkItemLinkTypeEndProxy( + ? new WorkItemLinkTypeEnd( ends.SingleOrDefault( p => p.ReferenceName.EndsWith("Reverse"))) { LinkType = type } @@ -205,8 +205,8 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo reverseId = CoreLinkTypes.Predecessor; } - ((WorkItemLinkTypeEndProxy)type.ForwardEnd).Id = -forwardId; - ((WorkItemLinkTypeEndProxy)type.ReverseEnd).Id = -reverseId; + ((WorkItemLinkTypeEnd)type.ForwardEnd).Id = -forwardId; + ((WorkItemLinkTypeEnd)type.ReverseEnd).Id = -reverseId; } } diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs similarity index 88% rename from src/Qwiq.Core.Rest/Proxies/WorkItemTypeProxy.cs rename to src/Qwiq.Core.Rest/Proxies/WorkItemType.cs index 95517370..254d2e3c 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs @@ -4,13 +4,13 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal class WorkItemTypeProxy : Microsoft.Qwiq.Proxies.WorkItemTypeProxy + internal class WorkItemType : Qwiq.WorkItemType { - internal WorkItemTypeProxy(WorkItemType type) + internal WorkItemType(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemType type) : base( type?.Name, type?.Description, - new Lazy(() => new FieldDefinitionCollectionProxy(type?.Fields)), + new Lazy(() => new FieldDefinitionCollection(type?.Fields)), NewWorkItemImpl) { if (type == null) throw new ArgumentNullException(nameof(type)); diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 67349a4c..14462c3b 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -97,20 +97,20 @@ - - - + + + - + - - - + + + - + diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index b6f9971d..0f03f0b1 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -28,7 +28,7 @@ public IWorkItemStore Create(AuthenticationOptions options) var tfsNative = ConnectToTfsCollection(options.Uri, credential.Credentials); var tfsProxy = ExceptionHandlingDynamicProxyFactory.Create( - new VssConnectionProxy(tfsNative)); + new VssConnectionAdapter(tfsNative)); options.Notifications.AuthenticationSuccess( new AuthenticationSuccessNotification(credential, tfsProxy)); diff --git a/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs b/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs index da9f1e45..f5611de9 100644 --- a/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs +++ b/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.Soap { internal static class LinkTypeEndMapper { - public static WorkItemLinkTypeEnd Map(WorkItemStore store, IWorkItemLinkTypeEnd end) + public static TeamFoundation.WorkItemTracking.Client.WorkItemLinkTypeEnd Map(WorkItemStore store, IWorkItemLinkTypeEnd end) { var linkType = store.WorkItemLinkTypes.Single(type => type.ReferenceName == end.LinkType.ReferenceName); return end.IsForwardLink ? linkType.ForwardEnd : linkType.ReverseEnd; diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs b/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs new file mode 100644 index 00000000..795872e9 --- /dev/null +++ b/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs @@ -0,0 +1,15 @@ +using System; + +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap.Proxies +{ + internal class FieldDefinition : Qwiq.FieldDefinition + { + internal FieldDefinition(Tfs.FieldDefinition fieldDefinition) + :base(fieldDefinition?.ReferenceName, fieldDefinition?.Name) + { + if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollectionProxy.cs b/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs similarity index 60% rename from src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollectionProxy.cs rename to src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs index d80c9f2a..909a9549 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollectionProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs @@ -6,11 +6,11 @@ namespace Microsoft.Qwiq.Soap.Proxies { - internal class FieldDefinitionCollectionProxy : Qwiq.Proxies.FieldDefinitionCollectionProxy + internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { - private readonly FieldDefinitionCollection _innerCollection; + private readonly TeamFoundation.WorkItemTracking.Client.FieldDefinitionCollection _innerCollection; - internal FieldDefinitionCollectionProxy(FieldDefinitionCollection innerCollection) + internal FieldDefinitionCollection(TeamFoundation.WorkItemTracking.Client.FieldDefinitionCollection innerCollection) { _innerCollection = innerCollection; } @@ -18,7 +18,7 @@ internal FieldDefinitionCollectionProxy(FieldDefinitionCollection innerCollectio public override int Count => _innerCollection.Count; public override IFieldDefinition this[string name] => ExceptionHandlingDynamicProxyFactory - .Create(new FieldDefinitionProxy(_innerCollection[name])); + .Create(new FieldDefinition(_innerCollection[name])); public override bool Contains(string fieldName) { @@ -27,10 +27,10 @@ public override bool Contains(string fieldName) public override IEnumerator GetEnumerator() { - return _innerCollection.Cast() + return _innerCollection.Cast() .Select( field => ExceptionHandlingDynamicProxyFactory.Create( - new FieldDefinitionProxy(field))) + new FieldDefinition(field))) .GetEnumerator(); } } diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionProxy.cs b/src/Qwiq.Core.Soap/Proxies/FieldDefinitionProxy.cs deleted file mode 100644 index 73b0c011..00000000 --- a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionProxy.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - internal class FieldDefinitionProxy : Microsoft.Qwiq.Proxies.FieldDefinitionProxy - { - internal FieldDefinitionProxy(Tfs.FieldDefinition fieldDefinition) - { - if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); - Name = fieldDefinition.Name; - ReferenceName = fieldDefinition.ReferenceName; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/NodeProxy.cs b/src/Qwiq.Core.Soap/Proxies/Node.cs similarity index 67% rename from src/Qwiq.Core.Soap/Proxies/NodeProxy.cs rename to src/Qwiq.Core.Soap/Proxies/Node.cs index 336d9f3a..c77bf120 100644 --- a/src/Qwiq.Core.Soap/Proxies/NodeProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/Node.cs @@ -4,9 +4,9 @@ namespace Microsoft.Qwiq.Soap.Proxies { - internal class NodeProxy : Microsoft.Qwiq.Proxies.NodeProxy + internal class Node : Qwiq.Node { - internal NodeProxy(Tfs.Node node) + internal Node(Tfs.Node node) { Id = node.Id; HasChildNodes = node.HasChildNodes; @@ -15,8 +15,8 @@ internal NodeProxy(Tfs.Node node) Name = node.Name; Path = node.Path; Uri = node.Uri; - ChildNodes = node.ChildNodes.Cast().Select(item => new NodeProxy(item)); - ParentNode = node.ParentNode != null ? new NodeProxy(node.ParentNode) : null; + ChildNodes = node.ChildNodes.Cast().Select(item => new Node(item)); + ParentNode = node.ParentNode != null ? new Node(node.ParentNode) : null; } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/ProjectProxy.cs b/src/Qwiq.Core.Soap/Proxies/Project.cs similarity index 77% rename from src/Qwiq.Core.Soap/Proxies/ProjectProxy.cs rename to src/Qwiq.Core.Soap/Proxies/Project.cs index a7c9fa97..13b74ea5 100644 --- a/src/Qwiq.Core.Soap/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/Project.cs @@ -8,9 +8,9 @@ namespace Microsoft.Qwiq.Soap.Proxies { - internal class ProjectProxy : Microsoft.Qwiq.Proxies.ProjectProxy + internal class Project : Qwiq.Project { - internal ProjectProxy(Tfs.Project project, IWorkItemStore store) + internal Project(Tfs.Project project, IWorkItemStore store) : base( project.Id, project.Guid, @@ -21,15 +21,15 @@ internal ProjectProxy(Tfs.Project project, IWorkItemStore store) () => project.WorkItemTypes.Cast() .Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new WorkItemTypeProxy(item)))), + new WorkItemType(item)))), new Lazy>( () => project.AreaRootNodes.Cast() .Select( - item => ExceptionHandlingDynamicProxyFactory.Create(new NodeProxy(item)))), + item => ExceptionHandlingDynamicProxyFactory.Create(new Node(item)))), new Lazy>( () => project.IterationRootNodes.Cast() .Select( - item => ExceptionHandlingDynamicProxyFactory.Create(new NodeProxy(item))))) + item => ExceptionHandlingDynamicProxyFactory.Create(new Node(item))))) { } } diff --git a/src/Qwiq.Core.Soap/Proxies/QueryProxy.cs b/src/Qwiq.Core.Soap/Proxies/QueryProxy.cs index 518e37fa..1489d7f7 100644 --- a/src/Qwiq.Core.Soap/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/QueryProxy.cs @@ -24,13 +24,13 @@ public IEnumerable RunQuery() public IEnumerable RunLinkQuery() { return _query.RunLinkQuery() - .Select(item => new WorkItemLinkInfoProxy(item)); + .Select(item => new WorkItemLinkInfo(item)); } public IEnumerable GetLinkTypes() { return _query.GetLinkTypes() - .Select(item => new WorkItemLinkTypeEndProxy(item)); + .Select(item => new WorkItemLinkTypeEnd(item)); } } } diff --git a/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs b/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs index c544fc8b..5084bd9c 100644 --- a/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs @@ -22,7 +22,7 @@ public IWorkItemLinkTypeEnd LinkTypeEnd { get { - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeEndProxy(_relatedLink.LinkTypeEnd)); + return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeEnd(_relatedLink.LinkTypeEnd)); } } diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs new file mode 100644 index 00000000..9390e876 --- /dev/null +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs @@ -0,0 +1,15 @@ +using System; + +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap.Proxies +{ + internal class WorkItemLinkInfo : Qwiq.WorkItemLinkInfo + { + internal WorkItemLinkInfo(Tfs.WorkItemLinkInfo item) + : base(item.SourceId, item.TargetId, item.LinkTypeId) + { + IsLocked = item.IsLocked; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfoProxy.cs deleted file mode 100644 index 6187ce62..00000000 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfoProxy.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - internal class WorkItemLinkInfoProxy : Microsoft.Qwiq.Proxies.WorkItemLinkInfoProxy - { - internal WorkItemLinkInfoProxy(Tfs.WorkItemLinkInfo item) - : base(new Lazy(() => item.LinkTypeId)) - { - IsLocked = item.IsLocked; - SourceId = item.SourceId; - TargetId = item.TargetId; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkType.cs similarity index 54% rename from src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeProxy.cs rename to src/Qwiq.Core.Soap/Proxies/WorkItemLinkType.cs index 2e4b1d45..a9088625 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkType.cs @@ -4,15 +4,16 @@ namespace Microsoft.Qwiq.Soap.Proxies { - internal class WorkItemLinkTypeProxy : Microsoft.Qwiq.Proxies.WorkItemLinkTypeProxy + internal class WorkItemLinkType : Qwiq.WorkItemLinkType { - internal WorkItemLinkTypeProxy(Tfs.WorkItemLinkType linkType) + internal WorkItemLinkType(Tfs.WorkItemLinkType linkType) : base( - new Lazy(() => new WorkItemLinkTypeEndProxy(linkType.ForwardEnd)), - new Lazy(() => new WorkItemLinkTypeEndProxy(linkType.ReverseEnd))) + linkType?.ReferenceName, + new Lazy(() => new WorkItemLinkTypeEnd(linkType?.ForwardEnd)), + new Lazy(() => new WorkItemLinkTypeEnd(linkType?.ReverseEnd))) { + if (linkType == null) throw new ArgumentNullException(nameof(linkType)); IsActive = linkType.IsActive; - ReferenceName = linkType.ReferenceName; IsDirectional = linkType.IsDirectional; } } diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs new file mode 100644 index 00000000..e9ce8451 --- /dev/null +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs @@ -0,0 +1,19 @@ +using System; + +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap.Proxies +{ + internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd + { + internal WorkItemLinkTypeEnd(Tfs.WorkItemLinkTypeEnd end) + : base(end?.ImmutableName, new Lazy(() => new WorkItemLinkTypeEnd(end?.OppositeEnd))) + { + if (end == null) throw new ArgumentNullException(nameof(end)); + Id = end.Id; + LinkType = new WorkItemLinkType(end.LinkType); + IsForwardLink = end.IsForwardLink; + Name = end.Name; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEndProxy.cs deleted file mode 100644 index 533ccbf9..00000000 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEndProxy.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - internal class WorkItemLinkTypeEndProxy : Microsoft.Qwiq.Proxies.WorkItemLinkTypeEndProxy - { - internal WorkItemLinkTypeEndProxy(Tfs.WorkItemLinkTypeEnd end) - : base(new Lazy(() => new WorkItemLinkTypeEndProxy(end.OppositeEnd))) - { - Id = end.Id; - LinkType = new WorkItemLinkTypeProxy(end.LinkType); - ImmutableName = end.ImmutableName; - IsForwardLink = end.IsForwardLink; - Name = end.Name; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs index 3f895067..3c724d6e 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs @@ -235,7 +235,7 @@ public string Title /// public IWorkItemType Type { - get { return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemTypeProxy(_item.Type)); } + get { return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(_item.Type)); } } /// diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs index 51a790a5..c8169ece 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs @@ -7,7 +7,7 @@ using Microsoft.TeamFoundation.Client; using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; -using WorkItemLinkTypeCollection = Microsoft.Qwiq.Proxies.WorkItemLinkTypeCollection; +using WorkItemLinkTypeCollection = Microsoft.Qwiq.WorkItemLinkTypeCollection; namespace Microsoft.Qwiq.Soap.Proxies { @@ -51,7 +51,7 @@ internal WorkItemStoreProxy( () => { return new WorkItemLinkTypeCollection( - _workItemStore.Value.WorkItemLinkTypes.Select(item => new WorkItemLinkTypeProxy(item))); + _workItemStore.Value.WorkItemLinkTypes.Select(item => new WorkItemLinkType(item))); }); } @@ -66,7 +66,7 @@ internal WorkItemStoreProxy( public IFieldDefinitionCollection FieldDefinitions => ExceptionHandlingDynamicProxyFactory .Create( - new FieldDefinitionCollectionProxy(_workItemStore.Value.FieldDefinitions)); + new FieldDefinitionCollection(_workItemStore.Value.FieldDefinitions)); public IEnumerable Projects { @@ -75,7 +75,7 @@ public IEnumerable Projects return _workItemStore.Value.Projects.Cast() .Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new ProjectProxy(item, this))); + new Project(item, this))); } } diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemType.cs similarity index 75% rename from src/Qwiq.Core.Soap/Proxies/WorkItemTypeProxy.cs rename to src/Qwiq.Core.Soap/Proxies/WorkItemType.cs index 5441b808..baccb787 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemType.cs @@ -6,13 +6,13 @@ namespace Microsoft.Qwiq.Soap.Proxies { - internal class WorkItemTypeProxy : Microsoft.Qwiq.Proxies.WorkItemTypeProxy + internal class WorkItemType : Qwiq.WorkItemType { - internal WorkItemTypeProxy(Tfs.WorkItemType type) + internal WorkItemType(Tfs.WorkItemType type) : base( type?.Name, type?.Description, - new Lazy(() => ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinitionCollectionProxy(type?.FieldDefinitions))), + new Lazy(() => ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinitionCollection(type?.FieldDefinitions))), () => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(type?.NewWorkItem())) ) { diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 1faaac35..a9661886 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -200,31 +200,31 @@ - - + + + - + - - - - + + + - + diff --git a/src/Qwiq.Core/CoreFieldRefNames.cs b/src/Qwiq.Core/CoreFieldRefNames.cs new file mode 100644 index 00000000..ee06ca3a --- /dev/null +++ b/src/Qwiq.Core/CoreFieldRefNames.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq +{ + public static class CoreFieldRefNames + { + public const string AreaId = "System.AreaId"; + + public const string AreaPath = "System.AreaPath"; + + public const string AssignedTo = "System.AssignedTo"; + + public const string AttachedFileCount = "System.AttachedFileCount"; + + public const string AuthorizedAs = "System.AuthorizedAs"; + + public const string AuthorizedDate = "System.AuthorizedDate"; + + public const string BoardColumn = "System.BoardColumn"; + + public const string BoardColumnDone = "System.BoardColumnDone"; + + public const string BoardLane = "System.BoardLane"; + + public const string ChangedBy = "System.ChangedBy"; + + public const string ChangedDate = "System.ChangedDate"; + + public const string CreatedBy = "System.CreatedBy"; + + public const string CreatedDate = "System.CreatedDate"; + + public const string Description = "System.Description"; + + public const string ExternalLinkCount = "System.ExternalLinkCount"; + + public const string History = "System.History"; + + public const string HyperLinkCount = "System.HyperLinkCount"; + + public const string Id = "System.Id"; + + public const string IsDeleted = "System.IsDeleted"; + + public const string IterationId = "System.IterationId"; + + public const string IterationPath = "System.IterationPath"; + + public const string LinkType = "System.Links.LinkType"; + + public const string NodeName = "System.NodeName"; + + public const string Reason = "System.Reason"; + + public const string RelatedLinkCount = "System.RelatedLinkCount"; + + public const string Rev = "System.Rev"; + + public const string RevisedDate = "System.RevisedDate"; + + public const string State = "System.State"; + + public const string Tags = "System.Tags"; + + public const string TeamProject = "System.TeamProject"; + + public const string Title = "System.Title"; + + public const string Watermark = "System.Watermark"; + + public const string WorkItemType = "System.WorkItemType"; + + + + public static IDictionary NameLookup { get; } = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { AreaId, "Area ID" }, + { AreaPath, "Area Path" }, + { AssignedTo, "Assigned To" }, + { AttachedFileCount,"Attached File Count"}, + { AuthorizedAs, "Authorized As" }, + { AuthorizedDate, "Authorized Date" }, + { BoardColumn, "Board Column" }, + { BoardColumnDone,"Board Column Done"}, + { BoardLane, "Board Lane" }, + { ChangedBy, "Changed By" }, + { ChangedDate, "Changed Date" }, + { CreatedBy, "Created By" }, + { CreatedDate, "Created Date" }, + { Description, "Description" }, + { ExternalLinkCount,"External Link Count"}, + { History, "History" }, + { HyperLinkCount, "Hyperlink Count" }, + { Id, "ID" }, + { IterationId, "Iteration ID" }, + { IterationPath, "Iteration Path" }, + { LinkType, "Link Type" }, + { NodeName, "Node Name" }, + { Reason, "Reason" }, + { RelatedLinkCount, "Related Link Count"}, + { Rev, "Rev" }, + { RevisedDate, "Revised Date" }, + { State, "State" }, + { Tags, "Tags" }, + { TeamProject, "Team Project" }, + { Title, "Title" }, + { Watermark, "Watermark" }, + { WorkItemType, "Work Item Type" } + }; + + public static IDictionary ReferenceNameLookup { get; } = NameLookup.ToDictionary( + k => k.Value, + e => e.Key, + StringComparer.OrdinalIgnoreCase); + + public static IEnumerable All => NameLookup.Keys; + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs b/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs index f1ae2035..33a867f6 100644 --- a/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs +++ b/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs @@ -1,113 +1,18 @@ using System.Collections.Generic; -using System.Runtime.InteropServices; namespace Microsoft.Qwiq { - public static class CoreLinkTypeReferenceNames - { - public static readonly string Related = "System.LinkTypes.Related"; - public static readonly string Hierarchy = "System.LinkTypes.Hierarchy"; - public static readonly string Dependency = "System.LinkTypes.Dependency"; - public static readonly string Duplicate = "System.LinkTypes.Duplicate"; - /// - /// private static array that keeps all reference names of corefield - /// so that caller can do enumeration easily - /// - private static readonly string[] _all = { - Related, - Hierarchy, - Dependency, - Duplicate - }; + public static class CoreLinkTypeReferenceNames + { + public const string Dependency = "System.LinkTypes.Dependency"; - /// Returns the set of all core link types. - public static IEnumerable All => _all; - } + public const string Duplicate = "System.LinkTypes.Duplicate"; - [StructLayout(LayoutKind.Sequential, Size = 1)] - public struct CoreLinkTypes - { - public const int Related = 1; - public const int Parent = 2; - public const int Child = -2; - public const int Predecessor = 3; - public const int Successor = -3; - } + public const string Hierarchy = "System.LinkTypes.Hierarchy"; - public static class CoreFieldRefNames - { - private static string[] m_all = new string[29] - { - "System.AuthorizedDate", - "System.AreaId", - "System.AreaPath", - "System.ExternalLinkCount", - "System.AssignedTo", - "System.AttachedFileCount", - "System.AuthorizedAs", - "System.ChangedBy", - "System.ChangedDate", - "System.CreatedBy", - "System.CreatedDate", - "System.Description", - "System.History", - "System.HyperLinkCount", - "System.Id", - "System.IterationId", - "System.IterationPath", - "System.Links.LinkType", - "System.NodeName", - "System.Reason", - "System.RelatedLinkCount", - "System.Rev", - "System.RevisedDate", - "System.State", - "System.TeamProject", - "System.Title", - "System.Watermark", - "System.WorkItemType", - "System.IsDeleted" - }; - public const string AreaId = "System.AreaId"; - public const string AreaPath = "System.AreaPath"; - public const string AssignedTo = "System.AssignedTo"; - public const string AttachedFileCount = "System.AttachedFileCount"; - public const string AuthorizedAs = "System.AuthorizedAs"; - public const string BoardColumn = "System.BoardColumn"; - public const string BoardColumnDone = "System.BoardColumnDone"; - public const string BoardLane = "System.BoardLane"; - public const string ChangedBy = "System.ChangedBy"; - public const string ChangedDate = "System.ChangedDate"; - public const string CreatedBy = "System.CreatedBy"; - public const string CreatedDate = "System.CreatedDate"; - public const string Description = "System.Description"; - public const string ExternalLinkCount = "System.ExternalLinkCount"; - public const string History = "System.History"; - public const string HyperLinkCount = "System.HyperLinkCount"; - public const string Id = "System.Id"; - public const string IterationId = "System.IterationId"; - public const string IterationPath = "System.IterationPath"; - public const string LinkType = "System.Links.LinkType"; - public const string NodeName = "System.NodeName"; - public const string Reason = "System.Reason"; - public const string RelatedLinkCount = "System.RelatedLinkCount"; - public const string Rev = "System.Rev"; - public const string RevisedDate = "System.RevisedDate"; - public const string State = "System.State"; - public const string AuthorizedDate = "System.AuthorizedDate"; - public const string TeamProject = "System.TeamProject"; - public const string Tags = "System.Tags"; - public const string Title = "System.Title"; - public const string WorkItemType = "System.WorkItemType"; - public const string Watermark = "System.Watermark"; - public const string IsDeleted = "System.IsDeleted"; + public const string Related = "System.LinkTypes.Related"; - public static IEnumerable All - { - get - { - return (IEnumerable)CoreFieldRefNames.m_all; - } + /// Returns the set of all core link types. + public static readonly IEnumerable All = new[] { Related, Hierarchy, Dependency, Duplicate }; } - } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/CoreLinkTypes.cs b/src/Qwiq.Core/CoreLinkTypes.cs new file mode 100644 index 00000000..f836fdd5 --- /dev/null +++ b/src/Qwiq.Core/CoreLinkTypes.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Microsoft.Qwiq +{ + [StructLayout(LayoutKind.Sequential, Size = 1)] + public struct CoreLinkTypes + { + public const int Related = 1; + + public const int Parent = 2; + + public const int Child = -2; + + public const int Predecessor = 3; + + public const int Successor = -3; + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs new file mode 100644 index 00000000..9cf82def --- /dev/null +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -0,0 +1,53 @@ +using System; + +using Microsoft.Qwiq.Proxies; + +namespace Microsoft.Qwiq +{ + /// + /// A facade for + /// + public class FieldDefinition : IFieldDefinition, IComparable, IEquatable + { + internal FieldDefinition(string referenceName, string name) + { + if (string.IsNullOrWhiteSpace(referenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); + + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + + Name = name; + ReferenceName = referenceName; + } + + public string Name { get; } + + public string ReferenceName { get; } + + public int CompareTo(IFieldDefinition other) + { + return FieldDefinitionComparer.Instance.Compare(this, other); + } + + public bool Equals(IFieldDefinition other) + { + return FieldDefinitionComparer.Instance.Equals(this, other); + } + + public override bool Equals(object obj) + { + return FieldDefinitionComparer.Instance.Equals(this, obj as IFieldDefinition); + } + + public override int GetHashCode() + { + return FieldDefinitionComparer.Instance.GetHashCode(this); + } + + public override string ToString() + { + return ReferenceName; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs b/src/Qwiq.Core/FieldDefinitionCollection.cs similarity index 78% rename from src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs rename to src/Qwiq.Core/FieldDefinitionCollection.cs index b376e6e8..51894a0d 100644 --- a/src/Qwiq.Core/Proxies/FieldDefinitionCollectionProxy.cs +++ b/src/Qwiq.Core/FieldDefinitionCollection.cs @@ -2,9 +2,14 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Qwiq.Proxies +using Microsoft.Qwiq.Proxies; + +namespace Microsoft.Qwiq { - public abstract class FieldDefinitionCollectionProxy : IFieldDefinitionCollection + /// + /// A facade over . + /// + public abstract class FieldDefinitionCollection : IFieldDefinitionCollection { public abstract int Count { get; } diff --git a/src/Qwiq.Core/IFieldDefinition.cs b/src/Qwiq.Core/IFieldDefinition.cs index 5fc017af..c7e56b95 100644 --- a/src/Qwiq.Core/IFieldDefinition.cs +++ b/src/Qwiq.Core/IFieldDefinition.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Qwiq +namespace Microsoft.Qwiq { public interface IFieldDefinition { diff --git a/src/Qwiq.Core/Proxies/NodeProxy.cs b/src/Qwiq.Core/Node.cs similarity index 89% rename from src/Qwiq.Core/Proxies/NodeProxy.cs rename to src/Qwiq.Core/Node.cs index 2b052c1a..b54f9e66 100644 --- a/src/Qwiq.Core/Proxies/NodeProxy.cs +++ b/src/Qwiq.Core/Node.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { - public class NodeProxy : INode, IComparer, IEquatable + public class Node : INode, IComparer, IEquatable { + internal Node() + { + } public IEnumerable ChildNodes { get; internal set; } public bool HasChildNodes { get; internal set; } diff --git a/src/Qwiq.Core/Proxies/ProjectProxy.cs b/src/Qwiq.Core/Project.cs similarity index 92% rename from src/Qwiq.Core/Proxies/ProjectProxy.cs rename to src/Qwiq.Core/Project.cs index 29d58ec0..b50f0878 100644 --- a/src/Qwiq.Core/Proxies/ProjectProxy.cs +++ b/src/Qwiq.Core/Project.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { - public class ProjectProxy : IProject, IComparer, IEquatable + public class Project : IProject, IComparer, IEquatable { private readonly Lazy> _area; @@ -11,7 +11,7 @@ public class ProjectProxy : IProject, IComparer, IEquatable private readonly Lazy> _wits; - internal ProjectProxy( + internal Project( int id, Guid guid, string name, @@ -31,7 +31,7 @@ internal ProjectProxy( _iteration = iteration ?? throw new ArgumentNullException(nameof(iteration)); } - private ProjectProxy() + private Project() { } diff --git a/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs b/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs deleted file mode 100644 index b7f62e8e..00000000 --- a/src/Qwiq.Core/Proxies/FieldDefinitionProxy.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Microsoft.Qwiq.Proxies -{ - public class FieldDefinitionProxy : IFieldDefinition, IComparable, IEquatable - { - public string Name { get; internal set; } - - public string ReferenceName { get; internal set; } - - public int CompareTo(IFieldDefinition other) - { - return FieldDefinitionComparer.Instance.Compare(this, other); - } - - public bool Equals(IFieldDefinition other) - { - return FieldDefinitionComparer.Instance.Equals(this, other); - } - - public override bool Equals(object obj) - { - return FieldDefinitionComparer.Instance.Equals(this, obj as IFieldDefinition); - } - - public override int GetHashCode() - { - return FieldDefinitionComparer.Instance.GetHashCode(this); - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 73ce1d6d..1a2047d0 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -203,7 +203,9 @@ + + @@ -225,6 +227,8 @@ + + @@ -263,29 +267,27 @@ + + - - - - - - - - - - + + + + + + @@ -298,7 +300,9 @@ - + + + diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs b/src/Qwiq.Core/WorkItemLinkInfo.cs similarity index 63% rename from src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs rename to src/Qwiq.Core/WorkItemLinkInfo.cs index 27087f39..98e0320b 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkInfoProxy.cs +++ b/src/Qwiq.Core/WorkItemLinkInfo.cs @@ -1,19 +1,21 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { - public class WorkItemLinkInfoProxy : IWorkItemLinkInfo, IEquatable, IComparable + public class WorkItemLinkInfo : IWorkItemLinkInfo, IEquatable, IComparable { private readonly Lazy _id; - internal WorkItemLinkInfoProxy(int id) - :this(new Lazy(()=>id)) + internal WorkItemLinkInfo(int sourceId, int targetId, int id) + : this(sourceId, targetId, new Lazy(() => id)) { } - internal WorkItemLinkInfoProxy(Lazy id) + internal WorkItemLinkInfo(int sourceId, int targetId, Lazy id) { + SourceId = sourceId; + TargetId = targetId; _id = id; } @@ -26,16 +28,16 @@ public bool Equals(IWorkItemLinkInfo other) public int LinkTypeId => _id.Value; - public int SourceId { get; internal set; } + public int SourceId { get; } - public int TargetId { get; internal set; } + public int TargetId { get; } - public static bool operator !=(WorkItemLinkInfoProxy x, WorkItemLinkInfoProxy y) + public static bool operator !=(WorkItemLinkInfo x, WorkItemLinkInfo y) { return !WorkItemLinkInfoComparer.Instance.Equals(x, y); } - public static bool operator ==(WorkItemLinkInfoProxy x, WorkItemLinkInfoProxy y) + public static bool operator ==(WorkItemLinkInfo x, WorkItemLinkInfo y) { return WorkItemLinkInfoComparer.Instance.Equals(x, y); } diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs b/src/Qwiq.Core/WorkItemLinkType.cs similarity index 68% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs rename to src/Qwiq.Core/WorkItemLinkType.cs index e05b4082..83206400 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeProxy.cs +++ b/src/Qwiq.Core/WorkItemLinkType.cs @@ -1,8 +1,8 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { - public class WorkItemLinkTypeProxy : IWorkItemLinkType, IComparable, IEquatable + public class WorkItemLinkType : IWorkItemLinkType, IComparable, IEquatable { private readonly Lazy _forwardFac; @@ -12,20 +12,25 @@ public class WorkItemLinkTypeProxy : IWorkItemLinkType, IComparable forward, Lazy reverse) + internal WorkItemLinkType(string referenceName, Lazy forward, Lazy reverse) + : this(referenceName) { _forwardFac = forward ?? throw new ArgumentNullException(nameof(forward)); _reverseFac = reverse ?? throw new ArgumentNullException(nameof(reverse)); } - internal WorkItemLinkTypeProxy() + internal WorkItemLinkType(string referenceName) { + ReferenceName = referenceName; + if (string.IsNullOrWhiteSpace(referenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); } public IWorkItemLinkTypeEnd ForwardEnd => _forward ?? _forwardFac.Value; @@ -34,7 +39,7 @@ internal WorkItemLinkTypeProxy() public bool IsDirectional { get; internal set; } - public string ReferenceName { get; internal set; } + public string ReferenceName { get; } public IWorkItemLinkTypeEnd ReverseEnd => _reverse ?? _reverseFac.Value; diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs similarity index 81% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeCollection.cs rename to src/Qwiq.Core/WorkItemLinkTypeCollection.cs index 60527f16..6e6444b9 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Qwiq.Proxies +using Microsoft.Qwiq.Proxies; + +namespace Microsoft.Qwiq { public class WorkItemLinkTypeCollection : IReadOnlyCollection { @@ -30,6 +32,8 @@ public IWorkItemLinkType this[string linkTypeReferenceName] { get { + if (string.IsNullOrWhiteSpace(linkTypeReferenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(linkTypeReferenceName)); if (_mapByName.TryGetValue(linkTypeReferenceName, out IWorkItemLinkType end)) return end; throw new Exception($"Work item link type {linkTypeReferenceName} does not exist."); @@ -48,11 +52,15 @@ IEnumerator IEnumerable.GetEnumerator() public bool Contains(string linkTypeReferenceName) { + if (string.IsNullOrWhiteSpace(linkTypeReferenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(linkTypeReferenceName)); return _mapByName.ContainsKey(linkTypeReferenceName); } public bool TryGetByName(string linkTypeReferenceName, out IWorkItemLinkType linkType) { + if (string.IsNullOrWhiteSpace(linkTypeReferenceName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(linkTypeReferenceName)); return _mapByName.TryGetValue(linkTypeReferenceName, out linkType); } diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs similarity index 50% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs rename to src/Qwiq.Core/WorkItemLinkTypeEnd.cs index 7cbba208..1d9d5cfd 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndProxy.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs @@ -1,26 +1,41 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { - public class WorkItemLinkTypeEndProxy : IWorkItemLinkTypeEnd + public class WorkItemLinkTypeEnd : IWorkItemLinkTypeEnd, + IEquatable, + IComparable { private readonly Lazy _opposite; - internal WorkItemLinkTypeEndProxy(Lazy oppositeEnd) - : this() + internal WorkItemLinkTypeEnd(string immutableName, Lazy oppositeEnd) + : this(immutableName) { _opposite = oppositeEnd; } - internal WorkItemLinkTypeEndProxy() + internal WorkItemLinkTypeEnd(string immutableName) { + ImmutableName = immutableName; + if (string.IsNullOrWhiteSpace(immutableName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(immutableName)); _opposite = new Lazy( () => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); } + public int CompareTo(IWorkItemLinkTypeEnd other) + { + return WorkItemLinkTypeEndComparer.Instance.Compare(this, other); + } + + public bool Equals(IWorkItemLinkTypeEnd other) + { + return WorkItemLinkTypeEndComparer.Instance.Equals(this, other); + } + public int Id { get; internal set; } - public string ImmutableName { get; internal set; } + public string ImmutableName { get; } public bool IsForwardLink { get; internal set; } diff --git a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs similarity index 91% rename from src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs rename to src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs index f597e521..661ac74c 100644 --- a/src/Qwiq.Core/Proxies/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Collections.Generic; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class WorkItemLinkTypeEndCollection : IReadOnlyCollection { @@ -31,6 +31,8 @@ public IWorkItemLinkTypeEnd this[string linkTypeEndName] { get { + if (string.IsNullOrWhiteSpace(linkTypeEndName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(linkTypeEndName)); IWorkItemLinkTypeEnd end; if (_mapByName.TryGetValue(linkTypeEndName, out end)) return end; diff --git a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs b/src/Qwiq.Core/WorkItemType.cs similarity index 85% rename from src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs rename to src/Qwiq.Core/WorkItemType.cs index a3ab31a8..777d99cf 100644 --- a/src/Qwiq.Core/Proxies/WorkItemTypeProxy.cs +++ b/src/Qwiq.Core/WorkItemType.cs @@ -1,17 +1,17 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { - public class WorkItemTypeProxy : IWorkItemType, IEquatable, IComparable + public class WorkItemType : IWorkItemType, IEquatable, IComparable { private readonly Lazy _fieldDefinitions; - internal WorkItemTypeProxy(string name, string description, Lazy fieldDefinitions) + internal WorkItemType(string name, string description, Lazy fieldDefinitions) : this(name, description, fieldDefinitions, null) { } - internal WorkItemTypeProxy( + internal WorkItemType( string name, string description, Lazy fieldDefinitions, diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs b/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs index 02589713..ada1f849 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs @@ -489,9 +489,12 @@ public IList Execute() private class WorkItemLinkGenerator : WorkItemGenerator { + private readonly MockWorkItemLinkType _linkType; + public WorkItemLinkGenerator(Func create, IEnumerable propertiesToSkip) : base(create, propertiesToSkip) { + _linkType = new MockWorkItemLinkType(CoreLinkTypeReferenceNames.Hierarchy); } protected override object GetRandomValue(Type propertyType) @@ -506,9 +509,9 @@ protected override object GetRandomValue(Type propertyType) for (var i = 0; i < Randomizer.Instance.Next(0, 10); i++) { retval.Add( - new MockWorkItemLink { - LinkTypeEnd = - new MockWorkItemLinkTypeEnd("Giver"), + new MockWorkItemLink + { + LinkTypeEnd = _linkType.ForwardEnd, RelatedWorkItemId = Randomizer.Instance.Next(1, 36) }); } @@ -519,9 +522,9 @@ protected override object GetRandomValue(Type propertyType) for (var i = 0; i < Randomizer.Instance.Next(0, 10); i++) { retval.Add( - new MockWorkItemLink { - LinkTypeEnd = - new MockWorkItemLinkTypeEnd("Taker"), + new MockWorkItemLink + { + LinkTypeEnd = _linkType.ReverseEnd, RelatedWorkItemId = Randomizer.Instance.Next(1, 36) }); } diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 6c5edd3f..885daae4 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -62,7 +62,7 @@ public ITfsTeamProjectCollection TeamProjectCollection public string UserSid => _innerWorkItemStore.UserSid; - public Microsoft.Qwiq.Proxies.WorkItemLinkTypeCollection WorkItemLinkTypes + public WorkItemLinkTypeCollection WorkItemLinkTypes { get { diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 7556afd8..78502c7c 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -142,7 +142,7 @@ public class when_the_issue_factory_parses_an_issue_with_links : WorkItemMapperC { public override void Given() { - var wit = new MockWorkItemType("Baz", WorkItemBackingStore.Select(s => new MockFieldDefinition(s.Key))); + var wit = new MockWorkItemType("Baz"); WorkItemStore = new MockWorkItemStore( diff --git a/test/Qwiq.Mocks/MockFieldDefinition.cs b/test/Qwiq.Mocks/MockFieldDefinition.cs index 9092c1cb..6692a3f8 100644 --- a/test/Qwiq.Mocks/MockFieldDefinition.cs +++ b/test/Qwiq.Mocks/MockFieldDefinition.cs @@ -2,24 +2,11 @@ namespace Microsoft.Qwiq.Mocks { - public class MockFieldDefinition : Microsoft.Qwiq.Proxies.FieldDefinitionProxy + public class MockFieldDefinition : FieldDefinition { - public MockFieldDefinition(string referenceName) - { - if (string.IsNullOrWhiteSpace(referenceName)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); - - ReferenceName = referenceName; - } - public MockFieldDefinition(string name, string referenceName) + : base(referenceName, name) { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - if (string.IsNullOrWhiteSpace(referenceName)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); - Name = name; - ReferenceName = referenceName; } } } diff --git a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs index 3bb609c2..7ffc8494 100644 --- a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs +++ b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockFieldDefinitionCollection : Microsoft.Qwiq.Proxies.FieldDefinitionCollectionProxy + public class MockFieldDefinitionCollection : FieldDefinitionCollection { private readonly Dictionary _fieldUsagesByName; diff --git a/test/Qwiq.Mocks/MockNode.cs b/test/Qwiq.Mocks/MockNode.cs index 16b8740d..bf48958c 100644 --- a/test/Qwiq.Mocks/MockNode.cs +++ b/test/Qwiq.Mocks/MockNode.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockNode : Microsoft.Qwiq.Proxies.NodeProxy + public class MockNode : Node { public MockNode(string name, bool isArea, bool isIteration) { diff --git a/test/Qwiq.Mocks/MockProject.cs b/test/Qwiq.Mocks/MockProject.cs index 983b94f8..a36de083 100644 --- a/test/Qwiq.Mocks/MockProject.cs +++ b/test/Qwiq.Mocks/MockProject.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockProject : ProjectProxy + public class MockProject : Project { public MockProject(IWorkItemStore store) : base( diff --git a/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs b/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs index 0c02b794..bcbb8029 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkInfo.cs @@ -1,22 +1,15 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkInfo : Microsoft.Qwiq.Proxies.WorkItemLinkInfoProxy + public class MockWorkItemLinkInfo : WorkItemLinkInfo { - public MockWorkItemLinkInfo() - : base(0) - { - } - public MockWorkItemLinkInfo(int sourceId, int targetId) : this(sourceId, targetId, 0) { } public MockWorkItemLinkInfo(int sourceId, int targetId, int linkTypeId) - : base(linkTypeId) + : base(sourceId, targetId, linkTypeId) { - SourceId = sourceId; - TargetId = targetId; } } } diff --git a/test/Qwiq.Mocks/MockWorkItemLinkType.cs b/test/Qwiq.Mocks/MockWorkItemLinkType.cs index cf572424..b7065128 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkType.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkType.cs @@ -2,16 +2,17 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkType : Microsoft.Qwiq.Proxies.WorkItemLinkTypeProxy + public class MockWorkItemLinkType : WorkItemLinkType { public MockWorkItemLinkType(string referenceName) + : base(referenceName) { string reverseName; int reverseId = 0; string forwardName; int forwardId = 0; - if (CoreLinkTypeReferenceNames.Hierarchy.Equals(referenceName,StringComparison.OrdinalIgnoreCase)) + if (CoreLinkTypeReferenceNames.Hierarchy.Equals(referenceName, StringComparison.OrdinalIgnoreCase)) { // The forward should be Child, but the ID used in CoreLinkTypes is -2, should be 2 forwardName = "Child"; @@ -47,7 +48,7 @@ public MockWorkItemLinkType(string referenceName) referenceName, "Reference name not supported in mock object."); } - ReferenceName = referenceName; + _forward = new MockWorkItemLinkTypeEnd(this, forwardName, true, -forwardId); _reverse = new MockWorkItemLinkTypeEnd(this, reverseName, false, -reverseId); } @@ -57,12 +58,11 @@ public MockWorkItemLinkType( bool isDirectional, string forwardEndName, string reverseEndName) + : base(referenceName) { if (string.IsNullOrEmpty(forwardEndName)) throw new ArgumentException("Value cannot be null or empty.", nameof(forwardEndName)); - if (string.IsNullOrEmpty(referenceName)) throw new ArgumentException("Value cannot be null or empty.", nameof(referenceName)); if (string.IsNullOrEmpty(reverseEndName)) throw new ArgumentException("Value cannot be null or empty.", nameof(reverseEndName)); IsDirectional = isDirectional; - ReferenceName = referenceName; _forward = new MockWorkItemLinkTypeEnd(this, forwardEndName, true); _reverse = new MockWorkItemLinkTypeEnd(this, reverseEndName, false); } diff --git a/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs b/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs index 9a5b7771..66d5e35f 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs @@ -2,28 +2,23 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkTypeEnd : Microsoft.Qwiq.Proxies.WorkItemLinkTypeEndProxy + public class MockWorkItemLinkTypeEnd : WorkItemLinkTypeEnd { - [Obsolete( - "This method has been deprecated and will be removed in a future release. See a constructor that takes (IWorkItemLinkType, String, Bool, Int32).")] - public MockWorkItemLinkTypeEnd(string name) - { - Name = name; - } - public MockWorkItemLinkTypeEnd(IWorkItemLinkType linkType, string name, bool isForward, int id = 0) + : base(GetValue(linkType, isForward)) { Id = id; LinkType = linkType ?? throw new ArgumentNullException(nameof(linkType)); Name = name ?? throw new ArgumentNullException(nameof(name)); IsForwardLink = isForward; + } - var referenceName = LinkType.ReferenceName; - if (!LinkType.IsDirectional) - { - ImmutableName = referenceName; - } - ImmutableName = referenceName + (IsForwardLink ? "-Forward" : "-Reverse"); + private static string GetValue(IWorkItemLinkType linkType, bool isForward) + { + if (linkType == null) throw new ArgumentNullException(nameof(linkType)); + var referenceName = linkType.ReferenceName; + if (!linkType.IsDirectional) return referenceName; + return referenceName + (isForward ? "-Forward" : "-Reverse"); } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 7402b430..1eeaa2c6 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -87,11 +87,11 @@ public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnume - public Microsoft.Qwiq.Proxies.WorkItemLinkTypeCollection WorkItemLinkTypes + public WorkItemLinkTypeCollection WorkItemLinkTypes { get { - return new Microsoft.Qwiq.Proxies.WorkItemLinkTypeCollection(CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s))); + return new WorkItemLinkTypeCollection(CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s))); } } diff --git a/test/Qwiq.Mocks/MockWorkItemType.cs b/test/Qwiq.Mocks/MockWorkItemType.cs index e81c84bc..edab021b 100644 --- a/test/Qwiq.Mocks/MockWorkItemType.cs +++ b/test/Qwiq.Mocks/MockWorkItemType.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemType : Microsoft.Qwiq.Proxies.WorkItemTypeProxy + public class MockWorkItemType : WorkItemType { [Obsolete( "This method has been deprecated and will be removed in a future release. See ctor(IWorkItemStore, String, String).")] @@ -13,10 +13,8 @@ public MockWorkItemType() { } - [Obsolete( - "This method has been deprecated and will be removed in a future release. See ctor(String, IEnumerable, String).")] - public MockWorkItemType(string name) - : this(name, CoreFieldRefNames.All.Select(s => new MockFieldDefinition(s))) + public MockWorkItemType(string name, string description = null) + : this(name, CoreFieldRefNames.All.Select(s => new MockFieldDefinition(s, CoreFieldRefNames.NameLookup[s])), description) { } From cb2762a6783e33f1978a790b001dd7d252e15bea Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 16:13:37 -0700 Subject: [PATCH 067/251] Convert ParentIdMapperStrategyTests to use CoreLinkTypes --- src/Qwiq.Core/CoreLinkTypes.cs | 2 ++ .../ParentIdMapperStrategyTests.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Qwiq.Core/CoreLinkTypes.cs b/src/Qwiq.Core/CoreLinkTypes.cs index f836fdd5..0173e97f 100644 --- a/src/Qwiq.Core/CoreLinkTypes.cs +++ b/src/Qwiq.Core/CoreLinkTypes.cs @@ -14,5 +14,7 @@ public struct CoreLinkTypes public const int Predecessor = 3; public const int Successor = -3; + + public const int Self = 0; } } \ No newline at end of file diff --git a/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs b/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs index fa3e1fac..2cd50935 100644 --- a/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs +++ b/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs @@ -40,7 +40,7 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo(2,1,-2) + new MockWorkItemLinkInfo(2,1,CoreLinkTypes.Child) }; var workItems = new[] @@ -141,7 +141,7 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo(2,1,-2) + new MockWorkItemLinkInfo(2,1,CoreLinkTypes.Child) }; var workItems = new[] @@ -194,8 +194,8 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo(2,1,-2), - new MockWorkItemLinkInfo(4,3,0) + new MockWorkItemLinkInfo(2,1,CoreLinkTypes.Child), + new MockWorkItemLinkInfo(4,3,CoreLinkTypes.Self) }; var workItems = new[] @@ -248,8 +248,8 @@ public override void Given() { var workItemLinks = new[] { - new MockWorkItemLinkInfo(2,1,-2), - new MockWorkItemLinkInfo(4,3,0) + new MockWorkItemLinkInfo(2,1,CoreLinkTypes.Child), + new MockWorkItemLinkInfo(4,3,CoreLinkTypes.Self) }; var wit = new MockWorkItemType("SimpleMockWorkItem"); From 258d83c5ede200e024e87cbf54ffc7ecdb837d42 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 28 Mar 2017 16:18:25 -0700 Subject: [PATCH 068/251] Update ParentIdMapperStrategyTests to use correct MockWorkItem ctor --- test/Qwiq.Mocks/MockWorkItemStore.cs | 2 +- .../ParentIdMapperStrategyTests.cs | 30 ++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 1eeaa2c6..8e64f12c 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -169,7 +169,7 @@ private void InitializeProject(IProject project) foreach (var wit in project.WorkItemTypes) { - var wi = new MockWorkItem(wit) { Id = _workItems.Count + 1, Type = wit }; + var wi = new MockWorkItem(wit) { Id = _workItems.Count + 1 }; _workItems.Add(wi); } diff --git a/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs b/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs index 2cd50935..a2d85f48 100644 --- a/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs +++ b/test/Qwiq.Relatives.Tests/ParentIdMapperStrategyTests.cs @@ -45,11 +45,10 @@ public override void Given() var workItems = new[] { - new MockWorkItem + new MockWorkItem(MockParentIdIssue.CustomWorkItemType) { Id = 2, - ChangedDate = new DateTime(2010, 10, 10), - Type = MockParentIdIssue.CustomWorkItemType + ChangedDate = new DateTime(2010, 10, 10) } }; @@ -103,11 +102,10 @@ public override void Given() { var workItems = new[] { - new MockWorkItem + new MockWorkItem(MockParentIdIssue.CustomWorkItemType) { Id = 2, - ChangedDate = new DateTime(2010, 10, 10), - Type = MockParentIdIssue.CustomWorkItemType + ChangedDate = new DateTime(2010, 10, 10) } }; @@ -146,17 +144,15 @@ public override void Given() var workItems = new[] { - new MockWorkItem + new MockWorkItem(MockParentIdIssue.CustomWorkItemType) { Id = 2, - ChangedDate = new DateTime(2010, 10, 10), - Type = MockParentIdIssue.CustomWorkItemType + ChangedDate = new DateTime(2010, 10, 10) }, - new MockWorkItem + new MockWorkItem(MockParentIdIssue.CustomWorkItemType) { Id = 4, - ChangedDate = new DateTime(2011, 11, 11), - Type = MockParentIdIssue.CustomWorkItemType + ChangedDate = new DateTime(2011, 11, 11) } }; @@ -200,17 +196,15 @@ public override void Given() var workItems = new[] { - new MockWorkItem + new MockWorkItem(MockParentIdIssue.CustomWorkItemType) { Id = 2, - ChangedDate = new DateTime(2010, 10, 10), - Type = MockParentIdIssue.CustomWorkItemType + ChangedDate = new DateTime(2010, 10, 10) }, - new MockWorkItem + new MockWorkItem(MockParentIdIssue.CustomWorkItemType) { Id = 4, - ChangedDate = new DateTime(2011, 11, 11), - Type = MockParentIdIssue.CustomWorkItemType + ChangedDate = new DateTime(2011, 11, 11) } }; From a7f6559fd1676baf232fa1fe5c4d46337258c1ad Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 29 Mar 2017 17:49:24 -0700 Subject: [PATCH 069/251] FieldCollection Updated base Core class with functionality commonly used in Mocks. This logic is reused for REST clients. --- .../IInternalTfsTeamProjectCollection.cs | 2 +- .../Proxies/FieldDefinitionCollection.cs | 47 +--- src/Qwiq.Core.Rest/Proxies/Project.cs | 3 +- src/Qwiq.Core.Rest/Proxies/QueryProxy.cs | 1 - .../Proxies/WorkItemLinkType.cs | 2 +- src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs | 4 +- .../Proxies/WorkItemStoreProxy.cs | 3 - src/Qwiq.Core.Rest/Proxies/WorkItemType.cs | 2 - src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 4 + .../NodeSelectExtensions.cs | 6 +- .../Proxies/ExternalLinkProxy.cs | 17 +- .../Proxies/FieldCollectionProxy.cs | 17 +- src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs | 5 + .../Proxies/FieldDefinitionCollection.cs | 20 +- src/Qwiq.Core.Soap/Proxies/FieldProxy.cs | 2 + src/Qwiq.Core.Soap/Proxies/Project.cs | 1 - src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs | 2 +- .../Proxies/WorkItemStoreProxy.cs | 1 - src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 1 + src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 10 +- src/Qwiq.Core/CoreField.cs | 39 +++ src/Qwiq.Core/CoreFieldRefNames.cs | 57 +++- .../Credentials/CredentialsNotifications.cs | 2 +- src/Qwiq.Core/FieldDefinition.cs | 11 +- src/Qwiq.Core/FieldDefinitionCollection.cs | 90 +++++- src/Qwiq.Core/FieldDefinitionComparer.cs | 4 +- .../FieldDefinitionNotExistException.cs | 8 +- src/Qwiq.Core/GenericComparer.cs | 2 +- ...Extensions.cs => IEnumerableExtensions.cs} | 11 +- src/Qwiq.Core/IField.cs | 1 + src/Qwiq.Core/IFieldCollection.cs | 10 +- src/Qwiq.Core/IFieldDefinition.cs | 1 + src/Qwiq.Core/IFieldDefinitionCollection.cs | 2 + src/Qwiq.Core/IProject.cs | 2 - .../ITeamFoundationIdentity.Extensions.cs | 2 + src/Qwiq.Core/IWorkItemStore.cs | 1 - src/Qwiq.Core/NodeComparer.cs | 2 + src/Qwiq.Core/Project.cs | 4 - src/Qwiq.Core/ProjectComparer.cs | 3 +- src/Qwiq.Core/Qwiq.Core.csproj | 8 +- src/Qwiq.Core/WorkItemFields.cs | 7 +- src/Qwiq.Core/WorkItemLinkInfoComparer.cs | 4 + src/Qwiq.Core/WorkItemLinkTypeCollection.cs | 2 - .../WorkItemLinkTypeEndCollection.cs | 2 + src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs | 4 + src/Qwiq.Core/WorkItemType.cs | 32 +-- src/Qwiq.Core/WorkItemTypeComparer.cs | 6 +- test/Qwiq.Core.Tests/Clock.cs | 2 +- test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 1 - test/Qwiq.Mapper.Tests/QueryProviderTests.cs | 7 +- test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 12 +- test/Qwiq.Mocks/CoreFieldDefinitions.cs | 23 ++ test/Qwiq.Mocks/MockField.cs | 70 ++++- test/Qwiq.Mocks/MockFieldCollection.cs | 66 ++++- test/Qwiq.Mocks/MockFieldDefinition.cs | 32 ++- .../MockFieldDefinitionCollection.cs | 45 +-- test/Qwiq.Mocks/MockProject.cs | 24 +- test/Qwiq.Mocks/MockWorkItem.cs | 265 ++++++------------ test/Qwiq.Mocks/MockWorkItemStore.cs | 119 ++++---- test/Qwiq.Mocks/MockWorkItemType.cs | 6 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 1 + ...ndationServerWorkItemQueryProviderTests.cs | 19 +- .../Qwiq.Tests.Common/ContextSpecification.cs | 5 +- 63 files changed, 671 insertions(+), 493 deletions(-) rename src/{Qwiq.Core => Qwiq.Core.Soap}/NodeSelectExtensions.cs (86%) create mode 100644 src/Qwiq.Core/CoreField.cs rename src/Qwiq.Core/{LinqExtensions.cs => IEnumerableExtensions.cs} (72%) create mode 100644 test/Qwiq.Mocks/CoreFieldDefinitions.cs diff --git a/src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs b/src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs index 9b3408a8..b5c4847c 100644 --- a/src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.Services.WebApi; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection { diff --git a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs b/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs index a688f3b9..2756a1b3 100644 --- a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs @@ -1,66 +1,25 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Rest.Proxies { internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { - private readonly Dictionary _fieldUsagesByName; - - private readonly Dictionary _fieldUsagesByReferenceName; - internal FieldDefinitionCollection(IEnumerable typeFields) : this(typeFields.Where(p => p != null).Select(s => s.Field)) { } internal FieldDefinitionCollection(IEnumerable typeFields) - : this(typeFields.Where(p=>p != null).Select(s => new FieldDefinition(s))) + : this(typeFields.Where(p => p != null).Select(s => new FieldDefinition(s))) { } private FieldDefinitionCollection(IEnumerable fieldDefinitions) + : base(fieldDefinitions) { - if (fieldDefinitions == null) throw new ArgumentNullException(nameof(fieldDefinitions)); - - _fieldUsagesByName = fieldDefinitions.ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); - _fieldUsagesByReferenceName = fieldDefinitions.ToDictionary( - k => k.ReferenceName, - e => e, - StringComparer.OrdinalIgnoreCase); - } - - public override int Count => _fieldUsagesByName.Count; - - public override IFieldDefinition this[string name] - { - get - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentException("Value cannot be null or empty.", nameof(name)); - - if (!_fieldUsagesByName.TryGetValue(name, out IFieldDefinition def)) - if (!_fieldUsagesByReferenceName.TryGetValue(name, out def)) - throw new FieldDefinitionNotExistException(name); - - return def; - } - } - - public override bool Contains(string fieldName) - { - if (string.IsNullOrEmpty(fieldName)) - throw new ArgumentException("Value cannot be null or empty.", nameof(fieldName)); - return _fieldUsagesByName.ContainsKey(fieldName) || _fieldUsagesByReferenceName.ContainsKey(fieldName); - } - - public override IEnumerator GetEnumerator() - { - return _fieldUsagesByName.Values.GetEnumerator(); } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/Project.cs b/src/Qwiq.Core.Rest/Proxies/Project.cs index 56a18c0b..45d5e5de 100644 --- a/src/Qwiq.Core.Rest/Proxies/Project.cs +++ b/src/Qwiq.Core.Rest/Proxies/Project.cs @@ -17,7 +17,6 @@ internal Project(TeamProjectReference project, WorkItemStoreProxy store) project.Id, project.Name, new Uri(project.Url), - store, new Lazy>( () => { @@ -32,7 +31,7 @@ internal Project(TeamProjectReference project, WorkItemStoreProxy store) .GetAwaiter() .GetResult(); - return new[] { new Node(result), }; + return new[] { new Node(result), }; }), new Lazy>( () => diff --git a/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs b/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs index 4541b8b6..3c6f0fa6 100644 --- a/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.Qwiq.Exceptions; -using Microsoft.Qwiq.Proxies; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Rest.Proxies diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs index 099f7850..c24ec963 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Rest.Proxies { - internal partial class WorkItemLinkType : Qwiq.WorkItemLinkType + internal class WorkItemLinkType : Qwiq.WorkItemLinkType { internal WorkItemLinkType(string referenceName) : base(referenceName) diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs index 01b5c64d..d78c39a1 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs @@ -43,7 +43,7 @@ public string ChangedBy public DateTime ChangedDate { - get => (DateTime)GetValue(CoreFieldRefNames.ChangedDate); + get => GetValue(CoreFieldRefNames.ChangedDate); set => SetValue(CoreFieldRefNames.ChangedDate, value); } @@ -55,7 +55,7 @@ public string CreatedBy public DateTime CreatedDate { - get => (DateTime)GetValue(CoreFieldRefNames.CreatedDate); + get => GetValue(CoreFieldRefNames.CreatedDate); set => SetValue(CoreFieldRefNames.CreatedDate, value); } diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs index 66581aee..0e745c5d 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs @@ -4,7 +4,6 @@ using System.Text.RegularExpressions; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Proxies; using Microsoft.TeamFoundation.Core.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; @@ -129,8 +128,6 @@ public IWorkItem Query(int id, DateTime? asOf = null) public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) { - if (dayPrecision) throw new NotSupportedException(); - // REVIEW: SOAP client catches a ValidationException here var query = _queryFactory.Value.Create(wiql, dayPrecision); return query.RunLinkQuery(); diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs b/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs index 254d2e3c..b6896c73 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs +++ b/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs @@ -1,7 +1,5 @@ using System; -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - namespace Microsoft.Qwiq.Rest.Proxies { internal class WorkItemType : Qwiq.WorkItemType diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index 0f03f0b1..21409ab9 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -103,9 +103,13 @@ private static IWorkItemStore CreateRestWorkItemStore(IInternalTfsTeamProjectCol return new WorkItemStoreProxy(() => tfs, QueryFactory.GetInstance); } + // ReSharper disable ClassNeverInstantiated.Local private class Nested + // ReSharper restore ClassNeverInstantiated.Local { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemStoreFactory Instance = new WorkItemStoreFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/NodeSelectExtensions.cs b/src/Qwiq.Core.Soap/NodeSelectExtensions.cs similarity index 86% rename from src/Qwiq.Core/NodeSelectExtensions.cs rename to src/Qwiq.Core.Soap/NodeSelectExtensions.cs index 7e3b218a..53d2d852 100644 --- a/src/Qwiq.Core/NodeSelectExtensions.cs +++ b/src/Qwiq.Core.Soap/NodeSelectExtensions.cs @@ -1,9 +1,9 @@ using System; using System.Globalization; -using Microsoft.TeamFoundation.WorkItemTracking.Client.Wiql; - -namespace Microsoft.Qwiq.Rest +// ReSharper disable CheckNamespace +namespace Microsoft.TeamFoundation.WorkItemTracking.Client.Wiql +// ReSharper restore CheckNamespace { public static class NodeSelectExtensions { diff --git a/src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs b/src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs index 81261450..d6ce317c 100644 --- a/src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs @@ -2,23 +2,16 @@ namespace Microsoft.Qwiq.Soap.Proxies { - public class ExternalLinkProxy : LinkProxy, IExternalLink + internal class ExternalLinkProxy : LinkProxy, IExternalLink { - private readonly Tfs.ExternalLink externalLink; - internal ExternalLinkProxy(Tfs.ExternalLink externalLink) : base(externalLink) { - this.externalLink = externalLink; + LinkedArtifactUri = externalLink.LinkedArtifactUri; + ArtifactLinkTypeName = externalLink.ArtifactLinkType.Name; } - public string LinkedArtifactUri - { - get { return this.externalLink.LinkedArtifactUri; } - } + public string LinkedArtifactUri { get; } - public string ArtifactLinkTypeName - { - get { return this.externalLink.ArtifactLinkType.Name; } - } + public string ArtifactLinkTypeName { get; } } } diff --git a/src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs b/src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs index 0c8afb03..bd45abc1 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs @@ -17,18 +17,23 @@ public FieldCollectionProxy(Tfs.FieldCollection innerCollection) _innerCollection = innerCollection; } - public IField this[string name] + public IField this[string name] => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection[name])); + + public int Count => _innerCollection.Count; + + public bool Contains(string name) { - get { return ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection[name])); } + return _innerCollection.Contains(name); } - public int Count + public IField TryGetById(int id) { - get { return _innerCollection.Count; } + return ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection.TryGetById(id))); } - public bool Contains(string fieldName) + + public IField GetById(int id) { - return _innerCollection.Contains(fieldName); + return ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection.GetById(id))); } public IEnumerator GetEnumerator() diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs b/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs index 795872e9..a8dede32 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs +++ b/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs @@ -11,5 +11,10 @@ internal FieldDefinition(Tfs.FieldDefinition fieldDefinition) { if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); } + + public static implicit operator FieldDefinition(Tfs.FieldDefinition fieldDefinition) + { + return new FieldDefinition(fieldDefinition); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs b/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs index 909a9549..3a0a2ec4 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs @@ -2,15 +2,15 @@ using System.Linq; using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.WorkItemTracking.Client; +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap.Proxies { internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { - private readonly TeamFoundation.WorkItemTracking.Client.FieldDefinitionCollection _innerCollection; + private readonly Tfs.FieldDefinitionCollection _innerCollection; - internal FieldDefinitionCollection(TeamFoundation.WorkItemTracking.Client.FieldDefinitionCollection innerCollection) + internal FieldDefinitionCollection(Tfs.FieldDefinitionCollection innerCollection) { _innerCollection = innerCollection; } @@ -25,13 +25,17 @@ public override bool Contains(string fieldName) return _innerCollection.Contains(fieldName); } + public override IFieldDefinition TryGetById(int id) + { + return ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(_innerCollection.TryGetById(id))); + } + public override IEnumerator GetEnumerator() { - return _innerCollection.Cast() - .Select( - field => ExceptionHandlingDynamicProxyFactory.Create( - new FieldDefinition(field))) - .GetEnumerator(); + return _innerCollection + .Cast() + .Select(field => ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(field))) + .GetEnumerator(); } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Proxies/FieldProxy.cs b/src/Qwiq.Core.Soap/Proxies/FieldProxy.cs index ef04ced3..8cf7104f 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/FieldProxy.cs @@ -25,6 +25,8 @@ internal FieldProxy(Tfs.Field field) public string Name => _field.Name; + public string ReferenceName => _field.ReferenceName; + public object OriginalValue { get => _field.OriginalValue; diff --git a/src/Qwiq.Core.Soap/Proxies/Project.cs b/src/Qwiq.Core.Soap/Proxies/Project.cs index 13b74ea5..26a8d60a 100644 --- a/src/Qwiq.Core.Soap/Proxies/Project.cs +++ b/src/Qwiq.Core.Soap/Proxies/Project.cs @@ -16,7 +16,6 @@ internal Project(Tfs.Project project, IWorkItemStore store) project.Guid, project.Name, project.Uri, - store, new Lazy>( () => project.WorkItemTypes.Cast() .Select( diff --git a/src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs b/src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs index 756fe79b..2c64aec5 100644 --- a/src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs @@ -34,7 +34,7 @@ public IDictionary Fields { get { - return _rev.Fields.Cast().ToDictionary(field => field.Name, field => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(field)) as IField); + return _rev.Fields.Cast().ToDictionary(field => field.Name, field => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(field))); } } diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs index c8169ece..00728e39 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs @@ -7,7 +7,6 @@ using Microsoft.TeamFoundation.Client; using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; -using WorkItemLinkTypeCollection = Microsoft.Qwiq.WorkItemLinkTypeCollection; namespace Microsoft.Qwiq.Soap.Proxies { diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index a9661886..a987f592 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -194,6 +194,7 @@ + diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 81065474..3c1718dd 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -72,10 +72,10 @@ public IWorkItemStore Create( { var options = new AuthenticationOptions(endpoint, AuthenticationType.Windows, type) - { - CreateCredentials = + { + CreateCredentials = t => credentials - }; + }; return Create(options); } @@ -100,9 +100,13 @@ private static IWorkItemStore CreateSoapWorkItemStore(IInternalTfsTeamProjectCol return new WorkItemStoreProxy(() => tfs, QueryFactory.GetInstance); } + // ReSharper disable ClassNeverInstantiated.Local private class Nested + // ReSharper restore ClassNeverInstantiated.Local { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemStoreFactory Instance = new WorkItemStoreFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/CoreField.cs b/src/Qwiq.Core/CoreField.cs new file mode 100644 index 00000000..00ee47ea --- /dev/null +++ b/src/Qwiq.Core/CoreField.cs @@ -0,0 +1,39 @@ +namespace Microsoft.Qwiq +{ + public enum CoreField + { + IsDeleted = -404, + IterationPath = -105, + IterationId = -104, + ExternalLinkCount = -57, + TeamProject = -42, + HyperLinkCount = -32, + AttachedFileCount = -31, + NodeName = -12, + AreaPath = -7, + RevisedDate = -5, + ChangedDate = -4, + Id = -3, + AreaId = -2, + AuthorizedAs = -1, + Title = 1, + State = 2, + AuthorizedDate = 3, + Watermark = 7, + Rev = 8, + ChangedBy = 9, + Reason = 22, + AssignedTo = 24, + WorkItemType = 25, + CreatedDate = 32, + CreatedBy = 33, + Description = 52, + History = 54, + RelatedLinkCount = 75, + Tags = 80, + BoardColumn = 90, + BoardColumnDone = 91, + BoardLane = 92, + LinkType = 100, + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/CoreFieldRefNames.cs b/src/Qwiq.Core/CoreFieldRefNames.cs index ee06ca3a..faecaa89 100644 --- a/src/Qwiq.Core/CoreFieldRefNames.cs +++ b/src/Qwiq.Core/CoreFieldRefNames.cs @@ -72,26 +72,66 @@ public static class CoreFieldRefNames public const string WorkItemType = "System.WorkItemType"; + public static IEnumerable All => NameLookup.Keys; + public static IReadOnlyDictionary CoreFieldIdLookup { get; } = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { AreaId, (int)CoreField.AreaId }, + { AreaPath, (int)CoreField.AreaPath }, + { AssignedTo, (int)CoreField.AssignedTo }, + { AttachedFileCount, (int)CoreField.AttachedFileCount }, + { AuthorizedAs, (int)CoreField.AuthorizedAs }, + { AuthorizedDate, (int)CoreField.AuthorizedDate }, + { BoardColumn, (int)CoreField.BoardColumn }, + { BoardColumnDone, (int)CoreField.BoardColumnDone }, + { BoardLane, (int)CoreField.BoardLane }, + { ChangedBy, (int)CoreField.ChangedBy }, + { ChangedDate, (int)CoreField.ChangedDate }, + { CreatedBy, (int)CoreField.CreatedBy }, + { CreatedDate, (int)CoreField.CreatedDate }, + { Description, (int)CoreField.Description }, + { ExternalLinkCount, (int)CoreField.ExternalLinkCount }, + { History, (int)CoreField.History }, + { HyperLinkCount, (int)CoreField.HyperLinkCount }, + { Id, (int)CoreField.Id }, + { IterationId, (int)CoreField.IterationId }, + { IterationPath, (int)CoreField.IterationPath }, + { LinkType, (int)CoreField.LinkType }, + { NodeName, (int)CoreField.NodeName }, + { Reason, (int)CoreField.Reason }, + { RelatedLinkCount, (int)CoreField.RelatedLinkCount }, + { Rev, (int)CoreField.Rev }, + { RevisedDate, (int)CoreField.RevisedDate }, + { State, (int)CoreField.State }, + { Tags, (int)CoreField.Tags }, + { TeamProject, (int)CoreField.TeamProject }, + { Title, (int)CoreField.Title }, + { Watermark, (int)CoreField.Watermark }, + { WorkItemType, (int)CoreField.WorkItemType } + }; - public static IDictionary NameLookup { get; } = + /// + /// Given a reference name from , get the corresponding friendly name. + /// + public static IReadOnlyDictionary NameLookup { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase) { { AreaId, "Area ID" }, { AreaPath, "Area Path" }, { AssignedTo, "Assigned To" }, - { AttachedFileCount,"Attached File Count"}, + { AttachedFileCount, "Attached File Count" }, { AuthorizedAs, "Authorized As" }, { AuthorizedDate, "Authorized Date" }, { BoardColumn, "Board Column" }, - { BoardColumnDone,"Board Column Done"}, + { BoardColumnDone, "Board Column Done" }, { BoardLane, "Board Lane" }, { ChangedBy, "Changed By" }, { ChangedDate, "Changed Date" }, { CreatedBy, "Created By" }, { CreatedDate, "Created Date" }, { Description, "Description" }, - { ExternalLinkCount,"External Link Count"}, + { ExternalLinkCount, "External Link Count" }, { History, "History" }, { HyperLinkCount, "Hyperlink Count" }, { Id, "ID" }, @@ -100,7 +140,7 @@ public static class CoreFieldRefNames { LinkType, "Link Type" }, { NodeName, "Node Name" }, { Reason, "Reason" }, - { RelatedLinkCount, "Related Link Count"}, + { RelatedLinkCount, "Related Link Count" }, { Rev, "Rev" }, { RevisedDate, "Revised Date" }, { State, "State" }, @@ -111,11 +151,12 @@ public static class CoreFieldRefNames { WorkItemType, "Work Item Type" } }; - public static IDictionary ReferenceNameLookup { get; } = NameLookup.ToDictionary( + /// + /// Given a friendly name for one of , get the corresponding reference name. + /// + public static IReadOnlyDictionary ReferenceNameLookup { get; } = NameLookup.ToDictionary( k => k.Value, e => e.Key, StringComparer.OrdinalIgnoreCase); - - public static IEnumerable All => NameLookup.Keys; } } \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/CredentialsNotifications.cs b/src/Qwiq.Core/Credentials/CredentialsNotifications.cs index ae339c66..ca78cfb9 100644 --- a/src/Qwiq.Core/Credentials/CredentialsNotifications.cs +++ b/src/Qwiq.Core/Credentials/CredentialsNotifications.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Credentials { /// - /// Specifies events which the invokes to enable developer control over the authentication process. + /// Specifies events which the invokes to enable developer control over the authentication process. /// public class CredentialsNotifications { diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs index 9cf82def..bd28760b 100644 --- a/src/Qwiq.Core/FieldDefinition.cs +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -1,7 +1,5 @@ using System; -using Microsoft.Qwiq.Proxies; - namespace Microsoft.Qwiq { /// @@ -9,6 +7,12 @@ namespace Microsoft.Qwiq /// public class FieldDefinition : IFieldDefinition, IComparable, IEquatable { + internal FieldDefinition(int id, string referenceName, string name) + : this(referenceName, name) + { + Id = id; + } + internal FieldDefinition(string referenceName, string name) { if (string.IsNullOrWhiteSpace(referenceName)) @@ -19,12 +23,15 @@ internal FieldDefinition(string referenceName, string name) Name = name; ReferenceName = referenceName; + Id = name.GetHashCode() ^ referenceName.GetHashCode(); } public string Name { get; } public string ReferenceName { get; } + public int Id { get; } + public int CompareTo(IFieldDefinition other) { return FieldDefinitionComparer.Instance.Compare(this, other); diff --git a/src/Qwiq.Core/FieldDefinitionCollection.cs b/src/Qwiq.Core/FieldDefinitionCollection.cs index 51894a0d..341d80c9 100644 --- a/src/Qwiq.Core/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core/FieldDefinitionCollection.cs @@ -1,29 +1,73 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Proxies; - namespace Microsoft.Qwiq { /// - /// A facade over . + /// A facade over . /// public abstract class FieldDefinitionCollection : IFieldDefinitionCollection { - public abstract int Count { get; } + private readonly Dictionary _fieldUsagesById; + + private readonly Dictionary _fieldUsagesByName; + + private readonly List _list; + + protected FieldDefinitionCollection() + { + } + + protected FieldDefinitionCollection(IEnumerable fieldDefinitions) + { + _list = new List(); + _fieldUsagesByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _fieldUsagesById = new Dictionary(); + + foreach (var field in fieldDefinitions) Add(field); + } - public abstract IFieldDefinition this[string name] { get; } + public virtual int Count => _list.Count; - public abstract bool Contains(string fieldName); + public virtual IFieldDefinition this[string name] + { + get + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Value cannot be null or empty.", nameof(name)); + + if (_fieldUsagesByName.TryGetValue(name, out int index)) return _list[index]; + + throw new FieldDefinitionNotExistException(name); + } + } - public abstract IEnumerator GetEnumerator(); + public virtual bool Contains(string fieldName) + { + if (string.IsNullOrEmpty(fieldName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(fieldName)); + return _fieldUsagesByName.ContainsKey(fieldName); + } + + public virtual IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + public virtual IFieldDefinition TryGetById(int id) + { + int index; + if (_fieldUsagesById.TryGetValue(id, out index)) return _list[index]; + return null; + } + public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) return true; @@ -41,5 +85,35 @@ public override int GetHashCode() return this.Aggregate(27, (current, f) => (13 * current) ^ f.GetHashCode()); } } + + private void Add(IFieldDefinition p) + { + var count = _list.Count; + try + { + _fieldUsagesByName.Add(p.Name, count); + } + catch (ArgumentException e) + { + throw new ArgumentException($"A field with the name {p.Name} already exists.", e); + } + try + { + _fieldUsagesByName.Add(p.ReferenceName, count); + } + catch (ArgumentException e) + { + throw new ArgumentException($"A field with the name {p.ReferenceName} already exists.", e); + } + try + { + _fieldUsagesById.Add(p.Id, count); + } + catch (ArgumentException e) + { + throw new ArgumentException($"A field with the ID {p.Id} already exists.", e); + } + _list.Add(p); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/FieldDefinitionComparer.cs b/src/Qwiq.Core/FieldDefinitionComparer.cs index 359a02ce..a19378e4 100644 --- a/src/Qwiq.Core/FieldDefinitionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionComparer.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Proxies +namespace Microsoft.Qwiq { public class FieldDefinitionComparer : GenericComparer { @@ -30,7 +30,9 @@ public override int GetHashCode(IFieldDefinition obj) private class Nested { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly FieldDefinitionComparer Instance = new FieldDefinitionComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/FieldDefinitionNotExistException.cs b/src/Qwiq.Core/FieldDefinitionNotExistException.cs index 84983251..a4910590 100644 --- a/src/Qwiq.Core/FieldDefinitionNotExistException.cs +++ b/src/Qwiq.Core/FieldDefinitionNotExistException.cs @@ -1,12 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace Microsoft.Qwiq.Exceptions +namespace Microsoft.Qwiq { - public class FieldDefinitionNotExistException : Exception + public class FieldDefinitionNotExistException : InvalidOperationException { public FieldDefinitionNotExistException(string name) : base($"TF26027: A field definition {name} in the work item type definition file does not exist. Add a definition for this field or remove the reference to the field and try again.") diff --git a/src/Qwiq.Core/GenericComparer.cs b/src/Qwiq.Core/GenericComparer.cs index 62f72e0c..48348b54 100644 --- a/src/Qwiq.Core/GenericComparer.cs +++ b/src/Qwiq.Core/GenericComparer.cs @@ -31,7 +31,7 @@ public virtual int Compare(T x, T y) return hasNextX == hasNextY ? 0 : -1; } - if (!GenericComparer.Equals(enumeratorX.Current, enumeratorY.Current)) + if (!Equals(enumeratorX.Current, enumeratorY.Current)) { return -1; } diff --git a/src/Qwiq.Core/LinqExtensions.cs b/src/Qwiq.Core/IEnumerableExtensions.cs similarity index 72% rename from src/Qwiq.Core/LinqExtensions.cs rename to src/Qwiq.Core/IEnumerableExtensions.cs index 34cfd9e8..a9d18a33 100644 --- a/src/Qwiq.Core/LinqExtensions.cs +++ b/src/Qwiq.Core/IEnumerableExtensions.cs @@ -1,9 +1,10 @@ -using System; -using System.Collections.Generic; - -namespace Microsoft.Qwiq.Rest +// ReSharper disable CheckNamespace +namespace System.Collections.Generic +// ReSharper restore CheckNamespace { - internal static class LinqExtensions + // ReSharper disable InconsistentNaming + internal static class IEnumerableExtensions + // ReSharper restore InconsistentNaming { public static IEnumerable> Partition(this IEnumerable source, int size) { diff --git a/src/Qwiq.Core/IField.cs b/src/Qwiq.Core/IField.cs index 62588528..5448f88b 100644 --- a/src/Qwiq.Core/IField.cs +++ b/src/Qwiq.Core/IField.cs @@ -8,6 +8,7 @@ public interface IField bool IsRequired { get; } bool IsValid { get; } string Name { get; } + string ReferenceName { get; } object OriginalValue { get; set; } ValidationState ValidationState { get; } bool IsChangedByUser { get; } diff --git a/src/Qwiq.Core/IFieldCollection.cs b/src/Qwiq.Core/IFieldCollection.cs index 48951ff9..19f5ce2c 100644 --- a/src/Qwiq.Core/IFieldCollection.cs +++ b/src/Qwiq.Core/IFieldCollection.cs @@ -1,13 +1,15 @@ -using System.Collections; using System.Collections.Generic; namespace Microsoft.Qwiq { - public interface IFieldCollection : IEnumerable + public interface IFieldCollection : IReadOnlyCollection { IField this[string name] { get; } - int Count { get; } - bool Contains(string fieldName); + bool Contains(string name); + + IField TryGetById(int id); + + IField GetById(int id); } } diff --git a/src/Qwiq.Core/IFieldDefinition.cs b/src/Qwiq.Core/IFieldDefinition.cs index c7e56b95..739e5c92 100644 --- a/src/Qwiq.Core/IFieldDefinition.cs +++ b/src/Qwiq.Core/IFieldDefinition.cs @@ -4,5 +4,6 @@ public interface IFieldDefinition { string Name { get; } string ReferenceName { get; } + int Id { get; } } } diff --git a/src/Qwiq.Core/IFieldDefinitionCollection.cs b/src/Qwiq.Core/IFieldDefinitionCollection.cs index 11664c2a..17776d6a 100644 --- a/src/Qwiq.Core/IFieldDefinitionCollection.cs +++ b/src/Qwiq.Core/IFieldDefinitionCollection.cs @@ -8,5 +8,7 @@ public interface IFieldDefinitionCollection : IEnumerable IFieldDefinition this[string name] { get; } bool Contains(string fieldName); + + IFieldDefinition TryGetById(int id); } } \ No newline at end of file diff --git a/src/Qwiq.Core/IProject.cs b/src/Qwiq.Core/IProject.cs index 91fc7f72..086883c1 100644 --- a/src/Qwiq.Core/IProject.cs +++ b/src/Qwiq.Core/IProject.cs @@ -15,8 +15,6 @@ public interface IProject string Name { get; } - IWorkItemStore Store { get; } - Uri Uri { get; } IEnumerable WorkItemTypes { get; } diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs index e131a660..2e819219 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs @@ -2,7 +2,9 @@ namespace Microsoft.Qwiq { + // ReSharper disable InconsistentNaming public static class ITeamFoundationIdentityExtensions + // ReSharper restore InconsistentNaming { public static string GetUserAlias(this ITeamFoundationIdentity identity) diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 50d80322..73f13809 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Proxies; namespace Microsoft.Qwiq { diff --git a/src/Qwiq.Core/NodeComparer.cs b/src/Qwiq.Core/NodeComparer.cs index 85abaf4c..6addc1e5 100644 --- a/src/Qwiq.Core/NodeComparer.cs +++ b/src/Qwiq.Core/NodeComparer.cs @@ -45,7 +45,9 @@ public override int GetHashCode(INode obj) private class Nested { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly NodeComparer Instance = new NodeComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/Project.cs b/src/Qwiq.Core/Project.cs index b50f0878..4cc45afc 100644 --- a/src/Qwiq.Core/Project.cs +++ b/src/Qwiq.Core/Project.cs @@ -16,7 +16,6 @@ internal Project( Guid guid, string name, Uri uri, - IWorkItemStore store, Lazy> wits, Lazy> area, Lazy> iteration) @@ -25,7 +24,6 @@ internal Project( Guid = guid; Name = name ?? throw new ArgumentNullException(nameof(name)); Uri = uri ?? throw new ArgumentNullException(nameof(uri)); - Store = store ?? throw new ArgumentNullException(nameof(store)); _wits = wits ?? throw new ArgumentNullException(nameof(wits)); _area = area ?? throw new ArgumentNullException(nameof(area)); _iteration = iteration ?? throw new ArgumentNullException(nameof(iteration)); @@ -55,8 +53,6 @@ public bool Equals(IProject other) public string Name { get; } - public IWorkItemStore Store { get; } - public Uri Uri { get; } public IEnumerable WorkItemTypes => _wits.Value; diff --git a/src/Qwiq.Core/ProjectComparer.cs b/src/Qwiq.Core/ProjectComparer.cs index 03764718..987e4287 100644 --- a/src/Qwiq.Core/ProjectComparer.cs +++ b/src/Qwiq.Core/ProjectComparer.cs @@ -1,4 +1,3 @@ -using Microsoft.Qwiq.Proxies; using System; using System.Linq; @@ -37,7 +36,9 @@ public override int GetHashCode(IProject obj) private class Nested { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly ProjectComparer Instance = new ProjectComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 1a2047d0..221f086a 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -203,6 +203,7 @@ + @@ -235,6 +236,7 @@ + @@ -266,10 +268,8 @@ - - @@ -300,9 +300,7 @@ - - - + diff --git a/src/Qwiq.Core/WorkItemFields.cs b/src/Qwiq.Core/WorkItemFields.cs index 8444baa8..282d89dc 100644 --- a/src/Qwiq.Core/WorkItemFields.cs +++ b/src/Qwiq.Core/WorkItemFields.cs @@ -1,12 +1,7 @@ -using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; - -namespace Microsoft.Qwiq +namespace Microsoft.Qwiq { public static class WorkItemFields { public const string Keywords = "Microsoft.VSTS.Common.Keywords"; } - - - } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs index e3ee8269..399d4529 100644 --- a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs @@ -30,9 +30,13 @@ public override int GetHashCode(IWorkItemLinkInfo obj) } } + // ReSharper disable ClassNeverInstantiated.Local private class Nested + // ReSharper restore ClassNeverInstantiated.Local { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemLinkInfoComparer Instance = new WorkItemLinkInfoComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs index 6e6444b9..a6f2fac0 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Proxies; - namespace Microsoft.Qwiq { public class WorkItemLinkTypeCollection : IReadOnlyCollection diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs index 661ac74c..2adbf976 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs @@ -4,6 +4,8 @@ namespace Microsoft.Qwiq { + + public class WorkItemLinkTypeEndCollection : IReadOnlyCollection { private readonly Dictionary _mapByName; diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs index 200af154..24424732 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs @@ -25,9 +25,13 @@ public override int GetHashCode(IWorkItemLinkTypeEnd obj) } } + // ReSharper disable ClassNeverInstantiated.Local private class Nested + // ReSharper restore ClassNeverInstantiated.Local { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemLinkTypeEndComparer Instance = new WorkItemLinkTypeEndComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/src/Qwiq.Core/WorkItemType.cs b/src/Qwiq.Core/WorkItemType.cs index 777d99cf..0f0c0316 100644 --- a/src/Qwiq.Core/WorkItemType.cs +++ b/src/Qwiq.Core/WorkItemType.cs @@ -4,30 +4,22 @@ namespace Microsoft.Qwiq { public class WorkItemType : IWorkItemType, IEquatable, IComparable { - private readonly Lazy _fieldDefinitions; - - internal WorkItemType(string name, string description, Lazy fieldDefinitions) - : this(name, description, fieldDefinitions, null) - { - } - internal WorkItemType( string name, string description, Lazy fieldDefinitions, - Func workItemFactory) + Func workItemFactory = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - _fieldDefinitions = fieldDefinitions ?? throw new ArgumentNullException(nameof(fieldDefinitions)); + FieldDefinitionFactory = () => fieldDefinitions.Value; WorkItemFactory = workItemFactory; Name = name; Description = description; } - public string Description { get; } - public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions.Value; - public string Name { get; } + protected internal Func FieldDefinitionFactory { get; internal set; } + protected internal Func WorkItemFactory { get; internal set; } public int CompareTo(IWorkItemType other) @@ -39,6 +31,18 @@ public bool Equals(IWorkItemType other) { return WorkItemTypeComparer.Instance.Equals(this, other); } + + public string Description { get; } + + public IFieldDefinitionCollection FieldDefinitions => FieldDefinitionFactory(); + + public string Name { get; } + + public IWorkItem NewWorkItem() + { + return WorkItemFactory(); + } + public override bool Equals(object obj) { return WorkItemTypeComparer.Instance.Equals(this, obj as IWorkItemType); @@ -49,10 +53,6 @@ public override int GetHashCode() return WorkItemTypeComparer.Instance.GetHashCode(this); } - public IWorkItem NewWorkItem() - { - return WorkItemFactory(); - } public override string ToString() { return Name; diff --git a/src/Qwiq.Core/WorkItemTypeComparer.cs b/src/Qwiq.Core/WorkItemTypeComparer.cs index 7e3850c9..4f3fe529 100644 --- a/src/Qwiq.Core/WorkItemTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeComparer.cs @@ -13,7 +13,7 @@ public override bool Equals(IWorkItemType x, IWorkItemType y) if (ReferenceEquals(y, null)) return false; return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) - && string.Equals(x.Description,y.Description,StringComparison.OrdinalIgnoreCase) + && string.Equals(x.Description, y.Description, StringComparison.OrdinalIgnoreCase) && x.FieldDefinitions.Equals(y.FieldDefinitions); } @@ -31,9 +31,13 @@ public override int GetHashCode(IWorkItemType obj) } } + // ReSharper disable ClassNeverInstantiated.Local private class Nested + // ReSharper restore ClassNeverInstantiated.Local { + // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemTypeComparer Instance = new WorkItemTypeComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit diff --git a/test/Qwiq.Core.Tests/Clock.cs b/test/Qwiq.Core.Tests/Clock.cs index 52815ca7..396d9e0a 100644 --- a/test/Qwiq.Core.Tests/Clock.cs +++ b/test/Qwiq.Core.Tests/Clock.cs @@ -34,7 +34,7 @@ public static TimeSpan GetTimeSpan(long start, long stop) private static TimeSpan GetTimeSpan(long start, long stop, long frequency) { - var seconds = 1.0d * (double)Math.Max(0L, stop - start) / (double)frequency; + var seconds = 1.0d * Math.Max(0L, stop - start) / frequency; var ticks = (long)Math.Round(seconds * 10000000d); return TimeSpan.FromTicks(ticks); } diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 0565efa4..fc7c235c 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -3,7 +3,6 @@ using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; -using Microsoft.Qwiq.Proxies; using Microsoft.Qwiq.Soap; using Microsoft.Qwiq.Soap.Proxies; using Microsoft.Qwiq.Tests.Common; diff --git a/test/Qwiq.Mapper.Tests/QueryProviderTests.cs b/test/Qwiq.Mapper.Tests/QueryProviderTests.cs index 3ca8d627..97d52fef 100644 --- a/test/Qwiq.Mapper.Tests/QueryProviderTests.cs +++ b/test/Qwiq.Mapper.Tests/QueryProviderTests.cs @@ -14,6 +14,8 @@ public abstract class SelectTests : QueryTestsBase protected override IWorkItemStore CreateWorkItemStore() { + + var workItems = new List { new MockWorkItem("SimpleMockWorkItem", new Dictionary @@ -55,7 +57,10 @@ protected override IWorkItemStore CreateWorkItemStore() new MockWorkItemLinkInfo(0, 5) }; - return new MockWorkItemStore(workItems, links); + var tpcMock = new MockTfsTeamProjectCollection(); + var projMock = new MockProject(workItems.Select(s=>s.Type).Distinct()); + + return new MockWorkItemStore(tpcMock, projMock, null, workItems, links); } } diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 78502c7c..599e5509 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -143,17 +143,17 @@ public class when_the_issue_factory_parses_an_issue_with_links : WorkItemMapperC public override void Given() { var wit = new MockWorkItemType("Baz"); - WorkItemStore = new MockWorkItemStore( new[] - { - new MockWorkItem(wit) {Id = 233 }, - new MockWorkItem(wit) {Id = 144 } - }); + { + new MockWorkItem(wit) {Id = 233 }, + new MockWorkItem(wit) {Id = 144 } + }); var related = new MockWorkItemLinkType("NS.SampleLink", true, "Taker", "Giver"); + var links = new ILink[] { new MockWorkItemLink @@ -170,7 +170,7 @@ public override void Given() SourceWorkItems = new IWorkItem[] { - new MockWorkItem(wit, WorkItemBackingStore) + new MockWorkItem("Baz", WorkItemBackingStore) { Links = new MockLinkCollection(links) } diff --git a/test/Qwiq.Mocks/CoreFieldDefinitions.cs b/test/Qwiq.Mocks/CoreFieldDefinitions.cs new file mode 100644 index 00000000..3ababf0c --- /dev/null +++ b/test/Qwiq.Mocks/CoreFieldDefinitions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Qwiq.Mocks +{ + public static class CoreFieldDefinitions + { + public static IFieldDefinition Id { get; } = new MockFieldDefinition((int)CoreField.Id, CoreFieldRefNames.NameLookup[CoreFieldRefNames.Id], CoreFieldRefNames.Id); + public static IFieldDefinition WorkItemType { get; } = new MockFieldDefinition((int)CoreField.WorkItemType, CoreFieldRefNames.NameLookup[CoreFieldRefNames.WorkItemType], CoreFieldRefNames.WorkItemType); + + public static IEnumerable All { get; } = CoreFieldRefNames + .ReferenceNameLookup + .Select(s => new MockFieldDefinition(CoreFieldRefNames.CoreFieldIdLookup[s.Value], s.Key, s.Value)); + + public static IDictionary NameLookup { get; } = All.ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); + public static IDictionary ReferenceNameLookup { get; } = All.ToDictionary(k => k.ReferenceName, e => e, StringComparer.OrdinalIgnoreCase); + + + } +} diff --git a/test/Qwiq.Mocks/MockField.cs b/test/Qwiq.Mocks/MockField.cs index 141022fd..0747958f 100644 --- a/test/Qwiq.Mocks/MockField.cs +++ b/test/Qwiq.Mocks/MockField.cs @@ -5,9 +5,39 @@ namespace Microsoft.Qwiq.Mocks public class MockField : IField { private const int MaxStringLength = 255; + private object _value; - public MockField(object value, object originalValue = null, ValidationState validationState = ValidationState.Valid, bool isChangedByUser = true) + public MockField(IFieldDefinition definition) + { + if (definition == null) throw new ArgumentNullException(nameof(definition)); + + Id = definition.Id; + Name = definition.Name; + ReferenceName = definition.ReferenceName; + } + + public MockField( + IFieldDefinition definition, + object value, + object originalValue = null, + ValidationState validationState = ValidationState.Valid, + bool isChangedByUser = true) + : this(definition) + { + Value = value; + OriginalValue = originalValue; + ValidationState = validationState; + IsChangedByUser = isChangedByUser; + IsEditable = true; + } + + [Obsolete("This method has been deprecated and will be removed in a future version.")] + public MockField( + object value, + object originalValue = null, + ValidationState validationState = ValidationState.Valid, + bool isChangedByUser = true) { Value = value; OriginalValue = originalValue; @@ -18,7 +48,10 @@ public MockField(object value, object originalValue = null, ValidationState vali public int Id { get; set; } + public bool IsChangedByUser { get; } + public bool IsDirty { get; private set; } + public bool IsEditable { get; set; } public bool IsRequired { get; set; } @@ -28,12 +61,14 @@ public MockField(object value, object originalValue = null, ValidationState vali public string Name { get; set; } public object OriginalValue { get; set; } + + public string ReferenceName { get; } + public ValidationState ValidationState { get; private set; } - public bool IsChangedByUser { get; } public object Value { - get { return _value; } + get => _value; set { _value = value; @@ -48,55 +83,66 @@ public object Value switch (ValidationState) { case ValidationState.InvalidNotEmpty: - if (string.IsNullOrEmpty(value as string)) - { - ValidationState = ValidationState.Valid; - } + if (string.IsNullOrEmpty(value as string)) ValidationState = ValidationState.Valid; break; case ValidationState.InvalidTooLong: var str = value as string; - if (str != null && str.Length <= MaxStringLength) - { - ValidationState = ValidationState.Valid; - } + if (str != null && str.Length <= MaxStringLength) ValidationState = ValidationState.Valid; break; + case ValidationState.Valid: break; + case ValidationState.InvalidEmpty: break; + case ValidationState.InvalidFormat: break; + case ValidationState.InvalidListValue: break; + case ValidationState.InvalidOldValue: break; + case ValidationState.InvalidNotOldValue: break; + case ValidationState.InvalidEmptyOrOldValue: break; + case ValidationState.InvalidNotEmptyOrOldValue: break; + case ValidationState.InvalidValueInOtherField: break; + case ValidationState.InvalidValueNotInOtherField: break; + case ValidationState.InvalidUnknown: break; + case ValidationState.InvalidDate: break; + case ValidationState.InvalidType: break; + case ValidationState.InvalidComputedField: break; + case ValidationState.InvalidPath: break; + case ValidationState.InvalidCharacters: break; + default: throw new ArgumentOutOfRangeException(); } } } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockFieldCollection.cs b/test/Qwiq.Mocks/MockFieldCollection.cs index ad87d8f2..826617f6 100644 --- a/test/Qwiq.Mocks/MockFieldCollection.cs +++ b/test/Qwiq.Mocks/MockFieldCollection.cs @@ -1,28 +1,55 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; namespace Microsoft.Qwiq.Mocks { public class MockFieldCollection : IFieldCollection { - private readonly IDictionary _values; + private readonly IDictionary _cache; - public MockFieldCollection(IDictionary values) + private readonly IFieldDefinitionCollection _definitions; + + public MockFieldCollection(IFieldDefinitionCollection definitions) { - _values = values; + _definitions = definitions; + _cache = new Dictionary(); } - public MockFieldCollection(ICollection fields) - : this(fields.ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase)) + public int Count => _definitions.Count; + + public IField this[string name] { + get + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return GetById(_definitions[name].Id); + } + } + public bool Contains(string name) + { + try + { + return _definitions.Contains(name); + } + // REVIEW: Catch a more specific exception + catch (Exception) + { + return false; + } + } + + public IField GetById(int id) + { + var byId = TryGetById(id); + if (byId == null) throw new ArgumentException($"Field {id} does not exist.", nameof(id)); + return byId; } public IEnumerator GetEnumerator() { - return _values.Values.GetEnumerator(); + return _cache.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -30,13 +57,24 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - public bool Contains(string fieldName) + public IField TryGetById(int id) { - return _values.ContainsKey(fieldName); + IField field; + if (_cache.TryGetValue(id, out field)) return field; + try + { + var def = _definitions.TryGetById(id); + if (def != null) + { + field = new MockField(def); + _cache[id] = field; + } + } + // REVIEW: Catch a more specific exception + catch (Exception) + { + } + return field; } - - public IField this[string name] => _values[name]; - - public int Count => _values.Count; } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockFieldDefinition.cs b/test/Qwiq.Mocks/MockFieldDefinition.cs index 6692a3f8..7227ac78 100644 --- a/test/Qwiq.Mocks/MockFieldDefinition.cs +++ b/test/Qwiq.Mocks/MockFieldDefinition.cs @@ -1,12 +1,38 @@ -using System; - -namespace Microsoft.Qwiq.Mocks +namespace Microsoft.Qwiq.Mocks { public class MockFieldDefinition : FieldDefinition { + public MockFieldDefinition(int id, string name, string referenceName) + : base(id, referenceName, name) + { + } + public MockFieldDefinition(string name, string referenceName) : base(referenceName, name) { } + + /// + /// Given a reference name or friendly name, attempt to look up from existing fields. If the field is not a core field, a new field definition is created. + /// + /// The reference name or friendly name of a field. + /// + public static IFieldDefinition Create(string name) + { + IFieldDefinition field; + if (CoreFieldDefinitions.NameLookup.TryGetValue(name, out field)) + { + return field; + } + + if (CoreFieldDefinitions.ReferenceNameLookup.TryGetValue(name, out field)) + { + return field; + } + + field = new MockFieldDefinition(name, $"Microsoft.Qwiq.Mocks.{name.Replace(" ", string.Empty)}"); + + return field; + } } } diff --git a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs index 7ffc8494..fd3fc165 100644 --- a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs +++ b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs @@ -2,54 +2,19 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Exceptions; - namespace Microsoft.Qwiq.Mocks { public class MockFieldDefinitionCollection : FieldDefinitionCollection { - private readonly Dictionary _fieldUsagesByName; - - private readonly Dictionary _fieldUsagesByReferenceName; - - public MockFieldDefinitionCollection(IEnumerable fieldDefinitions) + public MockFieldDefinitionCollection(IWorkItemStore store) + : base(store?.Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s)) { - if (fieldDefinitions == null) throw new ArgumentNullException(nameof(fieldDefinitions)); - - _fieldUsagesByName = fieldDefinitions.Where(p => !string.IsNullOrWhiteSpace(p.Name)).ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); - _fieldUsagesByReferenceName = fieldDefinitions.Where(p => !string.IsNullOrWhiteSpace(p.ReferenceName)).ToDictionary( - k => k.ReferenceName, - e => e, - StringComparer.OrdinalIgnoreCase); + if (store == null) throw new ArgumentNullException(nameof(store)); } - public override int Count => _fieldUsagesByName.Count; - - public override IFieldDefinition this[string name] - { - get - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentException("Value cannot be null or empty.", nameof(name)); - - if (!_fieldUsagesByName.TryGetValue(name, out IFieldDefinition def)) - if (!_fieldUsagesByReferenceName.TryGetValue(name, out def)) - throw new FieldDefinitionNotExistException(name); - - return def; - } - } - - public override bool Contains(string fieldName) - { - if (string.IsNullOrEmpty(fieldName)) - throw new ArgumentException("Value cannot be null or empty.", nameof(fieldName)); - return _fieldUsagesByName.ContainsKey(fieldName) || _fieldUsagesByReferenceName.ContainsKey(fieldName); - } - - public override IEnumerator GetEnumerator() + public MockFieldDefinitionCollection(IEnumerable fieldDefinitions) + : base(fieldDefinitions) { - return _fieldUsagesByName.Values.GetEnumerator(); } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockProject.cs b/test/Qwiq.Mocks/MockProject.cs index a36de083..962d089d 100644 --- a/test/Qwiq.Mocks/MockProject.cs +++ b/test/Qwiq.Mocks/MockProject.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; -using Microsoft.Qwiq.Proxies; - namespace Microsoft.Qwiq.Mocks { public class MockProject : Project { - public MockProject(IWorkItemStore store) + public MockProject() : base( BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0), Guid.NewGuid(), - "Mock", + "Mock Project", new Uri("http://localhost"), - store, new Lazy>( () => new[] { @@ -29,9 +26,20 @@ public MockProject(IWorkItemStore store) { } - [Obsolete("This method has been deprecated and will be removed in a future release. See ctor(IWorkItemStore).")] - public MockProject() - : this(new MockWorkItemStore()) + public MockProject(IEnumerable workItemTypes) + : base( + BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0), + Guid.NewGuid(), + "Mock Project", + new Uri("http://localhost"), + new Lazy>(() => workItemTypes), + new Lazy>(() => new[] { CreateNodes(true) }), + new Lazy>(() => new[] { CreateNodes(false) })) + { + } + + public MockProject(IWorkItemType workItemType) + : this(new[] { workItemType }) { } diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index 6a2b922e..b3cca3d8 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.Qwiq.Exceptions; + namespace Microsoft.Qwiq.Mocks { [Serializable] @@ -11,21 +13,23 @@ public class MockWorkItem : IWorkItem { internal bool PartialOpenWasCalled = false; private readonly ICollection _links; - private readonly Dictionary _properties; + private IWorkItemType _type; + private IEnumerable _revisions; + [Obsolete( "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] public MockWorkItem() - : this((string)null, null as IEnumerable) + : this("Mock") { } [Obsolete( "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] public MockWorkItem(string workItemType = null) - : this(workItemType, null as IEnumerable) + : this(workItemType, null) { } @@ -36,186 +40,135 @@ public MockWorkItem(IDictionary fields) { } - [Obsolete( - "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] + /// + /// Creates a new work item type of the specified name and new field definitions for that type based on the supplied fields. + /// + /// + /// public MockWorkItem(string workItemType = null, IDictionary fields = null) - : this(workItemType, fields?.Select(p => new MockField(p.Value, p.Value) { Name = p.Key })) - { - } - - [Obsolete( - "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] - internal MockWorkItem(string workItemType = null, IEnumerable fields = null) - :this(string.IsNullOrEmpty(workItemType) ? new MockWorkItemType() : new MockWorkItemType(workItemType), fields) - { - } - - public MockWorkItem(IWorkItemType type) - : this(type, type.FieldDefinitions.ToDictionary(p => p.Name, e => (object)null)) + : this( + new MockWorkItemType( + workItemType ?? "Mock", + CoreFieldDefinitions.All.Union(fields?.Select(p => MockFieldDefinition.Create(p.Key)) ?? Enumerable.Empty())), + fields) { } public MockWorkItem(IWorkItemType type, IDictionary fields = null) - : this(type, fields?.Select(p => new MockField(p.Value, p.Value) { Name = p.Key })) + : this(type, fields?.Select(p => new MockField(type.FieldDefinitions[p.Key], p.Value, p.Value)) ?? Enumerable.Empty()) { } public MockWorkItem(IWorkItemType type, IEnumerable fields) { - _links = new MockLinkCollection(); - _properties = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (fields == null) throw new ArgumentNullException(nameof(fields)); + Type = type ?? throw new ArgumentNullException(nameof(type)); - if (fields != null) + foreach (var field in fields) { - foreach (var prop in fields) - { - _properties[prop.Name] = prop; - } + var f = Fields[field.ReferenceName ?? field.Name]; + f.Value = field.Value; + f.OriginalValue = field.OriginalValue; } - Type = type; + + _links = new MockLinkCollection(); Revisions = Enumerable.Empty(); + ApplyRules(false); } public string AreaPath { - get { return (string)GetValue("Area Path"); } - set - { - SetValue("Area Path", value); - SetValue("System.AreaPath", value); - } + get => GetValue(CoreFieldRefNames.AreaPath); + set => SetValue(CoreFieldRefNames.AreaPath, value); } public string AssignedTo { - get - { - return (string)GetValue("Assigned To"); - } - set - { - SetValue("Assigned To", value); - SetValue("System.AssignedTo", value); - } + get => GetValue(CoreFieldRefNames.AssignedTo); + set => SetValue(CoreFieldRefNames.AssignedTo, value); } public int AttachedFileCount { - get { return (int)GetValue("Attached File Count"); } - set { SetValue("Attached File Count", value); } + get => (int)GetValue(CoreFieldRefNames.AttachedFileCount); + set => SetValue(CoreFieldRefNames.AttachedFileCount, value); } - public IEnumerable Attachments - { - get { throw new NotImplementedException(); } - } + public IEnumerable Attachments => throw new NotImplementedException(); public string ChangedBy { - get { return (string)GetValue("Changed By"); } - set - { - SetValue("Changed By", value); - SetValue("System.ChangedBy", value); - } + get => GetValue(CoreFieldRefNames.ChangedBy); + set => SetValue(CoreFieldRefNames.ChangedBy, value); } public DateTime ChangedDate { - get { return (DateTime)GetValue("Changed Date"); } - set - { - SetValue("Changed Date", value); - SetValue("System.ChangedDate", value); - } + get => (DateTime)GetValue(CoreFieldRefNames.ChangedDate); + set => SetValue(CoreFieldRefNames.ChangedDate, value); } public string CreatedBy { - get { return (string)GetValue("Created By"); } - set - { - SetValue("Created By", value); - SetValue("Microsoft.VSTS.Common.CreatedBy", value); - } + get => GetValue(CoreFieldRefNames.CreatedBy); + set => SetValue(CoreFieldRefNames.CreatedBy, value); } public DateTime CreatedDate { - get { return (DateTime)GetValue("Created Date"); } - set - { - SetValue("Created Date", value); - SetValue("Microsoft.VSTS.Common.CreatedDate", value); - } + get => (DateTime)GetValue(CoreFieldRefNames.CreatedDate); + set => SetValue(CoreFieldRefNames.CreatedDate, value); } public string Description { - get { return (string)GetValue("Description"); } - set - { - SetValue("Description", value); - SetValue("System.Description", value); - } + get => GetValue(CoreFieldRefNames.Description); + set => SetValue(CoreFieldRefNames.Description, value); } public int ExternalLinkCount { - get { return (int)GetValue("External Link Count"); } - set { SetValue("External Link Count", value); } + get => (int)GetValue(CoreFieldRefNames.ExternalLinkCount); + set => SetValue(CoreFieldRefNames.ExternalLinkCount, value); } - public IFieldCollection Fields => new MockFieldCollection(_properties); + private IFieldCollection _fields; + + public IFieldCollection Fields => _fields ?? (_fields = new MockFieldCollection(Type.FieldDefinitions)); public string History { - get { return GetValue("History") as string ?? string.Empty; } - set - { - SetValue("History", value); - SetValue("System.History", value); - } + get => GetValue(CoreFieldRefNames.History) as string ?? string.Empty; + set => SetValue(CoreFieldRefNames.History, value); } public int HyperLinkCount { - get { return (int)GetValue("Hyper Link Count"); } - set { SetValue("Hyper Link Count", value); } + get => GetValue(CoreFieldRefNames.HyperLinkCount); + set => SetValue(CoreFieldRefNames.HyperLinkCount, value); } public int Id { - get { return ((int?)GetValue("Id")).GetValueOrDefault(0); } - set - { - SetValue("Id", value); - SetValue("System.Id", value); - } + get => ((int?)GetValue(CoreFieldRefNames.Id)).GetValueOrDefault(0); + set => SetValue(CoreFieldRefNames.Id, value); } public bool IsDirty { - get { return _properties.Select(p => p.Value.IsDirty).Any(); } + get { return Fields.Any(p => p.IsDirty); } } public string IterationPath { - get - { - return (string)GetValue("Iteration Path"); - } - set - { - SetValue("Iteration Path", value); - SetValue("System.IterationPath", value); - } + get => GetValue(CoreFieldRefNames.IterationPath); + set => SetValue(CoreFieldRefNames.IterationPath, value); } public string Keywords { - get { return (string)GetValue("Keywords"); } - set { SetValue("Keywords", value); } + get => GetValue(WorkItemFields.Keywords); + set => SetValue(WorkItemFields.Keywords, value); } public ICollection Links { get; set; } @@ -224,7 +177,7 @@ public string Keywords public string ReproSteps { - get { return (string)GetValue("Repro Steps"); } + get => GetValue("Repro Steps"); set { SetValue("Repro Steps", value); @@ -234,85 +187,62 @@ public string ReproSteps public long Rev { - get - { - return (long)GetValue("Rev"); - } - set - { - SetValue("Rev", value); - SetValue("System.Rev", value); - } + get => GetValue(CoreFieldRefNames.Rev); + set => SetValue(CoreFieldRefNames.Rev, value); } public DateTime RevisedDate { - get { return (DateTime)GetValue("Revised Date"); } - set { SetValue("Revised Date", value); } + get => GetValue(CoreFieldRefNames.RevisedDate); + set => SetValue(CoreFieldRefNames.RevisedDate, value); } public long Revision { - get { return (long)GetValue("Revision"); } - set { SetValue("Revision", value); } + get => (long)GetValue("Revision"); + set => SetValue("Revision", value); } public IEnumerable Revisions { - get { return (IEnumerable)GetValue("Revisions"); } - set { SetValue("Revisions", value); } + get => _revisions; + set => _revisions = value; } public string State { - get { return (string)GetValue("State"); } - set - { - SetValue("State", value); - SetValue("System.State", value); - } + get => GetValue(CoreFieldRefNames.State); + set => SetValue(CoreFieldRefNames.State, value); } public string Tags { - get { return (string)GetValue("Tags"); } - set { SetValue("Tags", value); } + get => GetValue(CoreFieldRefNames.Tags); + set => SetValue(CoreFieldRefNames.Tags, value); } public string Title { - get { return (string)GetValue("Title"); } - set - { - SetValue("Title", value); - SetValue("System.Title", value); - } + get => GetValue(CoreFieldRefNames.Title); + set => SetValue(CoreFieldRefNames.Title, value); } public IWorkItemType Type { - get - { - return _type; - } + get => _type; set { _type = value; - SetValue("System.WorkItemType", value.Name); - SetValue("Work Item Type", value.Name); + SetValue(CoreFieldRefNames.WorkItemType, value.Name); } } - public Uri Uri - { - get { return (Uri)GetValue("Uri"); } - set { SetValue("Uri", value); } - } + public Uri Uri { get; set; } public object this[string name] { - get { return GetValue(name); } - set { SetValue(name, value); } + get => GetValue(name); + set => SetValue(name, value); } public void Close() @@ -380,44 +310,27 @@ public void Save(SaveFlags flags) public IEnumerable Validate() { - var invalidFields = _properties.Where(p => !p.Value.IsValid).Select(p => p.Value).ToArray(); + var invalidFields = Fields.Where(p => !p.IsValid).Select(p => p).ToArray(); return invalidFields.Any() ? invalidFields : null; } - //public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd end, IWorkItem target) - //{ - // return new MockWorkItemLink(end) - // { - // RelatedWorkItemId = target.Id, - // LinkSubType = "Related" - // }; - //} - //public IHyperlink CreateHyperlink(string location) - //{ - // return new MockHyperlink(location); - //} + private T GetValue(string field) + { + return (T)GetValue(field); + } private object GetValue(string field) { - IField val; - return _properties.TryGetValue(field, out val) - ? val.Value - : null; + if (field == null) throw new ArgumentNullException(nameof(field)); + return Fields[field].Value; } private void SetValue(string field, object value) { - IField val; - if (_properties.TryGetValue(field, out val)) - { - val.Value = value; - } - else - { - _properties.Add(field, new MockField(value, value) { Name = field }); - } + if (field == null) throw new ArgumentNullException(nameof(field)); + Fields[field].Value = value; } public void ApplyRules(bool doNotUpdateChangedBy = false) diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 8e64f12c..82b73171 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -14,55 +14,81 @@ public class MockWorkItemStore : IWorkItemStore private readonly IEnumerable _links; - private IDictionary _lookup; + private readonly Lazy _storeDefinitions; - private IList _workItems; + private readonly IDictionary _lookup; + + private readonly IList _workItems; - [Obsolete("This method has been deprecated and will be removed in a future release. See ctor(ITfsTeamProjectCollection).")] public MockWorkItemStore() : this(new MockTfsTeamProjectCollection()) { } public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection) + : this(teamProjectCollection, new MockProject()) { - TeamProjectCollection = teamProjectCollection - ?? throw new ArgumentNullException(nameof(teamProjectCollection)); - - TimeZone = teamProjectCollection.TimeZone; - - var project = new MockProject(this); - - InitializeProject(project); } - public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IProject project) - : this(teamProjectCollection) + [Obsolete( + "This method has been deprecated and will be removed in a future release. See ctor(ITfsTeamProjectCollection, IProject, IEnumerable, IEnumerable, IEnumerable).")] + public MockWorkItemStore(IEnumerable workItems, IEnumerable links = null) + : this(new MockTfsTeamProjectCollection(), new MockProject(), null, workItems, links) { - if (project == null) throw new ArgumentNullException(nameof(project)); - InitializeProject(project); } - [Obsolete("This method has been deprecated and will be removed in a future release. See ctor(ITfsTeamProjectCollection, IEnumerable).")] - public MockWorkItemStore(IEnumerable workItems) - : this() + public MockWorkItemStore( + ITfsTeamProjectCollection teamProjectCollection, + IProject project, + IEnumerable linkTypes = null, + IEnumerable workItems = null, + IEnumerable links = null) { - _workItems = new List(workItems); - _lookup = _workItems.ToDictionary(k => k.Id, e => e); - } + TeamProjectCollection = teamProjectCollection + ?? throw new ArgumentNullException(nameof(teamProjectCollection)); + if (project == null) throw new ArgumentNullException(nameof(project)); + TimeZone = teamProjectCollection.TimeZone; - [Obsolete("This method has been deprecated and will be removed in a future release. See ctor(ITfsTeamProjectCollection, IEnumerable).")] - public MockWorkItemStore(IEnumerable workItems, IEnumerable links) - : this(workItems) - { - _links = links; - } + WorkItemLinkTypes = new WorkItemLinkTypeCollection( + linkTypes ?? CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s))); + + if (workItems == null) + { + Projects = new[] { project }; + _workItems = new List(); + + foreach (var wit in project.WorkItemTypes) + { + var wi = new MockWorkItem(wit) { Id = _workItems.Count + 1 }; + _workItems.Add(wi); + } + } + else + { + _workItems = workItems.ToList(); + + var missing = _workItems + .Select(s => s.Type) + .Distinct(WorkItemTypeComparer.Instance) + .Where(wit => !project.WorkItemTypes.Contains(wit, WorkItemTypeComparer.Instance)) + .ToList(); + + if (missing.Any()) + { + Trace.TraceWarning( + "Project {0} is missing the following work item type definitions: {1}.", + project.Name, + string.Join(", ", missing)); + // Add the missing WITs to the incoming project + project = new MockProject(project.WorkItemTypes.Union(missing).Distinct()); + } + + Projects = new[] { project }; + } - public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnumerable workItems) - : this(teamProjectCollection) - { - _workItems = new List(workItems); _lookup = _workItems.ToDictionary(k => k.Id, e => e); + _links = links?.ToList() ?? Enumerable.Empty(); + _storeDefinitions = new Lazy(() => new MockFieldDefinitionCollection(this)); } public bool SimulateQueryTimes { get; set; } @@ -71,11 +97,11 @@ public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnume public TfsCredentials AuthorizedCredentials => null; - public IFieldDefinitionCollection FieldDefinitions => new MockFieldDefinitionCollection(Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s)); + public IFieldDefinitionCollection FieldDefinitions => _storeDefinitions.Value; - public IEnumerable Projects { get; set; } + public IEnumerable Projects { get; } - public ITfsTeamProjectCollection TeamProjectCollection { get; set; } + public ITfsTeamProjectCollection TeamProjectCollection { get; } public TimeZone TimeZone { get; } @@ -85,15 +111,7 @@ public MockWorkItemStore(ITfsTeamProjectCollection teamProjectCollection, IEnume public string UserSid => TeamProjectCollection.AuthorizedIdentity.Descriptor.Identifier; - - - public WorkItemLinkTypeCollection WorkItemLinkTypes - { - get - { - return new WorkItemLinkTypeCollection(CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s))); - } - } + public WorkItemLinkTypeCollection WorkItemLinkTypes { get; } public void Dispose() { @@ -160,20 +178,5 @@ protected void Dispose(bool disposing) { } } - - private void InitializeProject(IProject project) - { - Projects = new[] { project }; - - _workItems = new List(); - - foreach (var wit in project.WorkItemTypes) - { - var wi = new MockWorkItem(wit) { Id = _workItems.Count + 1 }; - _workItems.Add(wi); - } - - _lookup = _workItems.ToDictionary(k => k.Id, e => e); - } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemType.cs b/test/Qwiq.Mocks/MockWorkItemType.cs index edab021b..feae4386 100644 --- a/test/Qwiq.Mocks/MockWorkItemType.cs +++ b/test/Qwiq.Mocks/MockWorkItemType.cs @@ -14,14 +14,16 @@ public MockWorkItemType() } public MockWorkItemType(string name, string description = null) - : this(name, CoreFieldRefNames.All.Select(s => new MockFieldDefinition(s, CoreFieldRefNames.NameLookup[s])), description) + : this(name, CoreFieldDefinitions.All, description) { } public MockWorkItemType(string name, IEnumerable fieldDefinitions, string description = null) - : this(name, new MockFieldDefinitionCollection(fieldDefinitions), description) + : base(name, description, null, null) { if (fieldDefinitions == null) throw new ArgumentNullException(nameof(fieldDefinitions)); + WorkItemFactory = () => new MockWorkItem(this); + FieldDefinitionFactory = () => new MockFieldDefinitionCollection(fieldDefinitions); } public MockWorkItemType(string name, IFieldDefinitionCollection fieldDefinitions, string description = null) diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index b8783a69..75aac30c 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -37,6 +37,7 @@ + diff --git a/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs b/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs index acd1495a..3fc678c9 100644 --- a/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs +++ b/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs @@ -48,29 +48,38 @@ public abstract class QueryReturnsResults : RelativesAwareTeamFoundationServerWo { public override void Given() { + var wit = new MockWorkItemType( + "SimpleMockWorkItem", + new [] + { + CoreFieldDefinitions.ReferenceNameLookup[CoreFieldRefNames.Id], + CoreFieldDefinitions.ReferenceNameLookup[CoreFieldRefNames.WorkItemType], + MockFieldDefinition.Create("Priority"), + }); + WorkItemStoreWorkItems = new List { - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 1}, {"Priority", 2} }), - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 2}, {"Priority", 4} }), - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 3}, {"Priority", 3} }), - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 4}, {"Priority", 4} }), - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 5}, {"Priority", 5} diff --git a/test/Qwiq.Tests.Common/ContextSpecification.cs b/test/Qwiq.Tests.Common/ContextSpecification.cs index dcda63a8..8a559edd 100644 --- a/test/Qwiq.Tests.Common/ContextSpecification.cs +++ b/test/Qwiq.Tests.Common/ContextSpecification.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Diagnostics; + +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Qwiq.Tests.Common { @@ -109,6 +111,7 @@ namespace Microsoft.Qwiq.Tests.Common /// http://channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DPR302 /// /// + [DebuggerStepThrough] public abstract class ContextSpecification : IContextSpecification { [TestInitialize] From 7de40668d7bc45d8edafa7ab9c2a2709608136ae Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 29 Mar 2017 17:54:45 -0700 Subject: [PATCH 070/251] Cache result of IFieldDefinitionCollection --- src/Qwiq.Core/WorkItemType.cs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Qwiq.Core/WorkItemType.cs b/src/Qwiq.Core/WorkItemType.cs index 0f0c0316..b803bfdd 100644 --- a/src/Qwiq.Core/WorkItemType.cs +++ b/src/Qwiq.Core/WorkItemType.cs @@ -4,6 +4,8 @@ namespace Microsoft.Qwiq { public class WorkItemType : IWorkItemType, IEquatable, IComparable { + private IFieldDefinitionCollection _fdc; + internal WorkItemType( string name, string description, @@ -18,6 +20,9 @@ internal WorkItemType( Description = description; } + public string Description { get; } + public IFieldDefinitionCollection FieldDefinitions => _fdc ?? (_fdc = FieldDefinitionFactory()); + public string Name { get; } protected internal Func FieldDefinitionFactory { get; internal set; } protected internal Func WorkItemFactory { get; internal set; } @@ -31,18 +36,6 @@ public bool Equals(IWorkItemType other) { return WorkItemTypeComparer.Instance.Equals(this, other); } - - public string Description { get; } - - public IFieldDefinitionCollection FieldDefinitions => FieldDefinitionFactory(); - - public string Name { get; } - - public IWorkItem NewWorkItem() - { - return WorkItemFactory(); - } - public override bool Equals(object obj) { return WorkItemTypeComparer.Instance.Equals(this, obj as IWorkItemType); @@ -53,6 +46,10 @@ public override int GetHashCode() return WorkItemTypeComparer.Instance.GetHashCode(this); } + public IWorkItem NewWorkItem() + { + return WorkItemFactory(); + } public override string ToString() { return Name; From 685db0f3f3e0ee92cfdb5af049b81fba0f3c6fa5 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 29 Mar 2017 18:21:24 -0700 Subject: [PATCH 071/251] Convert tests to supported mock API --- ...entityAwareAttributeMapperStrategyTests.cs | 10 +++++---- test/Qwiq.Mapper.Tests/QueryProviderTests.cs | 14 ++++++------ test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 22 ++++++++++++------- test/Qwiq.Mocks/MockWorkItem.cs | 9 +++----- test/Qwiq.Mocks/MockWorkItemType.cs | 2 +- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs index 9c953bfa..4733284b 100644 --- a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs +++ b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs @@ -39,7 +39,9 @@ public override void Given() : new MockIdentityManagementService(Identities)); var sourceWorkItems = new[] { - new MockWorkItem(new Dictionary + new MockWorkItem( + new MockWorkItemType("Baz", new[]{MockFieldDefinition.Create(MockIdentityType.BackingField)}), + new Dictionary { { MockIdentityType.BackingField, IdentityFieldBackingValue } }) @@ -50,7 +52,7 @@ public override void Given() public override void When() { - _strategy.Map(typeof (MockIdentityType), _workItemMappings, null); + _strategy.Map(typeof(MockIdentityType), _workItemMappings, null); } } @@ -124,7 +126,7 @@ public void set_on_an_identity_property_should_be_called_once() [TestClass] public class given_a_work_item_with_defined_fields_when_the_field_names_to_properties_are_retrieved : ContextSpecification { - private readonly Type _identityType = typeof (MockIdentityType); + private readonly Type _identityType = typeof(MockIdentityType); private IDictionary Expected { get; set; } private IDictionary Actual { get; set; } @@ -153,6 +155,6 @@ public void only_valid_fields_and_properties_are_retrieved() } } - + } diff --git a/test/Qwiq.Mapper.Tests/QueryProviderTests.cs b/test/Qwiq.Mapper.Tests/QueryProviderTests.cs index 97d52fef..2b5f954c 100644 --- a/test/Qwiq.Mapper.Tests/QueryProviderTests.cs +++ b/test/Qwiq.Mapper.Tests/QueryProviderTests.cs @@ -14,34 +14,34 @@ public abstract class SelectTests : QueryTestsBase protected override IWorkItemStore CreateWorkItemStore() { - + var wit = new MockWorkItemType("SimpleMockWorkItem", new []{MockFieldDefinition.Create("ID"), MockFieldDefinition.Create("IntField")}); var workItems = new List { - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 1}, {"IntField", 2} }), - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 2}, {"IntField", 4} }) , - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 3}, {"IntField", 3} }) , - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 4}, {"IntField", 4} }) , - new MockWorkItem("SimpleMockWorkItem", new Dictionary + new MockWorkItem(wit, new Dictionary { {"ID", 5}, {"IntField", 5} @@ -58,7 +58,7 @@ protected override IWorkItemStore CreateWorkItemStore() }; var tpcMock = new MockTfsTeamProjectCollection(); - var projMock = new MockProject(workItems.Select(s=>s.Type).Distinct()); + var projMock = new MockProject(wit); return new MockWorkItemStore(tpcMock, projMock, null, workItems, links); } diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 599e5509..e414b043 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -87,7 +87,7 @@ public class when_an_issue_is_mapped_with_a_field_containing_null_that_does_not_ { public override void Given() { - SourceWorkItems = new[] { new MockWorkItem("MissingFields", WorkItemBackingStore) }; + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("MissingFields", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; WorkItemStore = new MockWorkItemStore(SourceWorkItems); base.Given(); } @@ -114,7 +114,7 @@ public class when_an_issue_is_mapped_with_a_field_containing_null_that_does_not_ { public override void Given() { - SourceWorkItems = new[] { new MockWorkItem("Default", WorkItemBackingStore) }; + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("MissingFields", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; WorkItemStore = new MockWorkItemStore(SourceWorkItems); base.Given(); } @@ -142,16 +142,22 @@ public class when_the_issue_factory_parses_an_issue_with_links : WorkItemMapperC { public override void Given() { - var wit = new MockWorkItemType("Baz"); + var wit = new MockWorkItemType("Baz", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)); + var tpcMock = new MockTfsTeamProjectCollection(); + var projMock = new MockProject(wit); + var related = new MockWorkItemLinkType("NS.SampleLink", true, "Taker", "Giver"); WorkItemStore = new MockWorkItemStore( + tpcMock, + projMock, + new[] { related }, new[] { new MockWorkItem(wit) {Id = 233 }, new MockWorkItem(wit) {Id = 144 } }); - var related = new MockWorkItemLinkType("NS.SampleLink", true, "Taker", "Giver"); + var links = new ILink[] @@ -170,7 +176,7 @@ public override void Given() SourceWorkItems = new IWorkItem[] { - new MockWorkItem("Baz", WorkItemBackingStore) + new MockWorkItem(wit, WorkItemBackingStore) { Links = new MockLinkCollection(links) } @@ -226,7 +232,7 @@ public override void Given() { WorkItemStore = new MockWorkItemStore(Enumerable.Empty()); - SourceWorkItems = new[] { new MockWorkItem("Baz", WorkItemBackingStore) }; + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("Baz", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; _expected = new MockModelSubclass { @@ -277,7 +283,7 @@ public override void Given() { WorkItemStore = new MockWorkItemStore(Enumerable.Empty()); - SourceWorkItems = new[] { new MockWorkItem("Baz", WorkItemBackingStore) }; + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("Baz", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; _expected = new MockModelWithNoType { @@ -311,7 +317,7 @@ public override void Given() { WorkItemStore = new MockWorkItemStore(Enumerable.Empty()); - SourceWorkItems = new[] { new MockWorkItem("Baz", WorkItemBackingStore) }; + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("Baz", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; _expected = new MockModelWithNoBacking { Id = int.Parse(WorkItemBackingStore["Id"].ToString()) }; base.Given(); diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index b3cca3d8..00b54471 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -40,16 +40,13 @@ public MockWorkItem(IDictionary fields) { } - /// - /// Creates a new work item type of the specified name and new field definitions for that type based on the supplied fields. - /// - /// - /// + [Obsolete( + "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] public MockWorkItem(string workItemType = null, IDictionary fields = null) : this( new MockWorkItemType( workItemType ?? "Mock", - CoreFieldDefinitions.All.Union(fields?.Select(p => MockFieldDefinition.Create(p.Key)) ?? Enumerable.Empty())), + CoreFieldDefinitions.All.Union(fields?.Keys.Select(MockFieldDefinition.Create) ?? Enumerable.Empty())), fields) { } diff --git a/test/Qwiq.Mocks/MockWorkItemType.cs b/test/Qwiq.Mocks/MockWorkItemType.cs index feae4386..04bed2dd 100644 --- a/test/Qwiq.Mocks/MockWorkItemType.cs +++ b/test/Qwiq.Mocks/MockWorkItemType.cs @@ -23,7 +23,7 @@ public MockWorkItemType(string name, IEnumerable fieldDefiniti { if (fieldDefinitions == null) throw new ArgumentNullException(nameof(fieldDefinitions)); WorkItemFactory = () => new MockWorkItem(this); - FieldDefinitionFactory = () => new MockFieldDefinitionCollection(fieldDefinitions); + FieldDefinitionFactory = () => new MockFieldDefinitionCollection(fieldDefinitions.Union(new[] { CoreFieldDefinitions.Id, CoreFieldDefinitions.WorkItemType })); } public MockWorkItemType(string name, IFieldDefinitionCollection fieldDefinitions, string description = null) From 789a786b811a113ec637d869fa45557be937da02 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 29 Mar 2017 18:57:38 -0700 Subject: [PATCH 072/251] Update "Proxy" objects Continuation of changes from 8952e118be1a68012bead291ef4087f9949a7cff --- .../{Proxies => }/FieldDefinition.cs | 2 +- .../FieldDefinitionCollection.cs | 2 +- src/Qwiq.Core.Rest/IdentityDescriptor.cs | 18 +++ ...ItemClassificationNodeProxy.cs => Node.cs} | 2 +- src/Qwiq.Core.Rest/{Proxies => }/Project.cs | 4 +- .../{Proxies/QueryProxy.cs => Query.cs} | 16 +- .../{Proxies => }/QueryFactory.cs | 12 +- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 29 ++-- ...tityProxy.cs => TeamFoundationIdentity.cs} | 34 +--- .../{Proxies => }/VssConnectionAdapter.cs | 4 +- .../{Proxies/WorkItemProxy.cs => WorkItem.cs} | 10 +- .../{Proxies => }/WorkItemLinkInfo.cs | 2 +- .../{Proxies => }/WorkItemLinkType.cs | 2 +- .../{Proxies => }/WorkItemLinkTypeEnd.cs | 2 +- ...WorkItemStoreProxy.cs => WorkItemStore.cs} | 20 +-- src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 3 +- .../{Proxies => }/WorkItemType.cs | 2 +- src/Qwiq.Core.Soap/Attachment.cs | 38 +++++ ...viceProxy.cs => CommonStructureService.cs} | 12 +- .../ExternalLinkProxy.cs => ExternalLink.cs} | 6 +- .../{Proxies/FieldProxy.cs => Field.cs} | 6 +- ...dCollectionProxy.cs => FieldCollection.cs} | 14 +- src/Qwiq.Core.Soap/FieldConflict.cs | 22 +++ .../{Proxies => }/FieldDefinition.cs | 2 +- .../FieldDefinitionCollection.cs | 3 +- src/Qwiq.Core.Soap/Hyperlink.cs | 17 ++ src/Qwiq.Core.Soap/IdentityDescriptor.cs | 18 +++ ...eProxy.cs => IdentityManagementService.cs} | 12 +- .../ItemAlreadyUpdatedOnServerException.cs | 3 +- src/Qwiq.Core.Soap/Link.cs | 19 +++ ...nkCollectionProxy.cs => LinkCollection.cs} | 16 +- src/Qwiq.Core.Soap/LinkMapper.cs | 7 +- src/Qwiq.Core.Soap/LinkTypeEndMapper.cs | 2 +- src/Qwiq.Core.Soap/{Proxies => }/Node.cs | 2 +- src/Qwiq.Core.Soap/NodeInfo.cs | 21 +++ src/Qwiq.Core.Soap/{Proxies => }/Project.cs | 4 +- src/Qwiq.Core.Soap/ProjectInfo.cs | 19 +++ ...ectPropertyProxy.cs => ProjectProperty.cs} | 6 +- src/Qwiq.Core.Soap/Proxies/AttachmentProxy.cs | 62 -------- .../Proxies/FieldConflictProxy.cs | 34 ---- src/Qwiq.Core.Soap/Proxies/HyperlinkProxy.cs | 20 --- .../Proxies/IdentityDescriptorProxy.cs | 24 --- src/Qwiq.Core.Soap/Proxies/LinkProxy.cs | 25 --- src/Qwiq.Core.Soap/Proxies/NodeInfoProxy.cs | 21 --- .../Proxies/ProjectInfoProxy.cs | 17 -- .../Proxies/RelatedLinkProxy.cs | 35 ----- .../{Proxies/QueryProxy.cs => Query.cs} | 8 +- .../{Proxies => }/QueryFactory.cs | 12 +- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 60 +++---- src/Qwiq.Core.Soap/RelatedLink.cs | 23 +++ .../{Proxies/RevisionProxy.cs => Revision.cs} | 27 ++-- ...tityProxy.cs => TeamFoundationIdentity.cs} | 12 +- ...onProxy.cs => TfsTeamProjectCollection.cs} | 15 +- .../{Proxies/WorkItemProxy.cs => WorkItem.cs} | 147 ++++++------------ .../{Proxies => }/WorkItemLinkInfo.cs | 4 +- .../{Proxies => }/WorkItemLinkType.cs | 2 +- .../{Proxies => }/WorkItemLinkTypeEnd.cs | 2 +- ...WorkItemStoreProxy.cs => WorkItemStore.cs} | 16 +- src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 9 +- .../{Proxies => }/WorkItemType.cs | 4 +- .../IdentityDescriptorProxyTests.cs | 12 +- .../IdentityManagementServiceProxyTests.cs | 4 +- test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 5 +- 63 files changed, 438 insertions(+), 575 deletions(-) rename src/Qwiq.Core.Rest/{Proxies => }/FieldDefinition.cs (90%) rename src/Qwiq.Core.Rest/{Proxies => }/FieldDefinitionCollection.cs (95%) create mode 100644 src/Qwiq.Core.Rest/IdentityDescriptor.cs rename src/Qwiq.Core.Rest/{Proxies/WorkItemClassificationNodeProxy.cs => Node.cs} (95%) rename src/Qwiq.Core.Rest/{Proxies => }/Project.cs (97%) rename src/Qwiq.Core.Rest/{Proxies/QueryProxy.cs => Query.cs} (93%) rename src/Qwiq.Core.Rest/{Proxies => }/QueryFactory.cs (79%) rename src/Qwiq.Core.Rest/{Proxies/TeamFoundationIdentityProxy.cs => TeamFoundationIdentity.cs} (66%) rename src/Qwiq.Core.Rest/{Proxies => }/VssConnectionAdapter.cs (91%) rename src/Qwiq.Core.Rest/{Proxies/WorkItemProxy.cs => WorkItem.cs} (95%) rename src/Qwiq.Core.Rest/{Proxies => }/WorkItemLinkInfo.cs (91%) rename src/Qwiq.Core.Rest/{Proxies => }/WorkItemLinkType.cs (93%) rename src/Qwiq.Core.Rest/{Proxies => }/WorkItemLinkTypeEnd.cs (91%) rename src/Qwiq.Core.Rest/{Proxies/WorkItemStoreProxy.cs => WorkItemStore.cs} (94%) rename src/Qwiq.Core.Rest/{Proxies => }/WorkItemType.cs (97%) create mode 100644 src/Qwiq.Core.Soap/Attachment.cs rename src/Qwiq.Core.Soap/{Proxies/CommonStructureServiceProxy.cs => CommonStructureService.cs} (82%) rename src/Qwiq.Core.Soap/{Proxies/ExternalLinkProxy.cs => ExternalLink.cs} (64%) rename src/Qwiq.Core.Soap/{Proxies/FieldProxy.cs => Field.cs} (88%) rename src/Qwiq.Core.Soap/{Proxies/FieldCollectionProxy.cs => FieldCollection.cs} (74%) create mode 100644 src/Qwiq.Core.Soap/FieldConflict.cs rename src/Qwiq.Core.Soap/{Proxies => }/FieldDefinition.cs (93%) rename src/Qwiq.Core.Soap/{Proxies => }/FieldDefinitionCollection.cs (97%) create mode 100644 src/Qwiq.Core.Soap/Hyperlink.cs create mode 100644 src/Qwiq.Core.Soap/IdentityDescriptor.cs rename src/Qwiq.Core.Soap/{Proxies/IdentityManagementServiceProxy.cs => IdentityManagementService.cs} (85%) create mode 100644 src/Qwiq.Core.Soap/Link.cs rename src/Qwiq.Core.Soap/{Proxies/LinkCollectionProxy.cs => LinkCollection.cs} (83%) rename src/Qwiq.Core.Soap/{Proxies => }/Node.cs (94%) create mode 100644 src/Qwiq.Core.Soap/NodeInfo.cs rename src/Qwiq.Core.Soap/{Proxies => }/Project.cs (92%) create mode 100644 src/Qwiq.Core.Soap/ProjectInfo.cs rename src/Qwiq.Core.Soap/{Proxies/ProjectPropertyProxy.cs => ProjectProperty.cs} (53%) delete mode 100644 src/Qwiq.Core.Soap/Proxies/AttachmentProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/FieldConflictProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/HyperlinkProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/IdentityDescriptorProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/LinkProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/NodeInfoProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/ProjectInfoProxy.cs delete mode 100644 src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs rename src/Qwiq.Core.Soap/{Proxies/QueryProxy.cs => Query.cs} (80%) rename src/Qwiq.Core.Soap/{Proxies => }/QueryFactory.cs (70%) create mode 100644 src/Qwiq.Core.Soap/RelatedLink.cs rename src/Qwiq.Core.Soap/{Proxies/RevisionProxy.cs => Revision.cs} (75%) rename src/Qwiq.Core.Soap/{Proxies/TeamFoundationIdentityProxy.cs => TeamFoundationIdentity.cs} (79%) rename src/Qwiq.Core.Soap/{Proxies/TfsTeamProjectCollectionProxy.cs => TfsTeamProjectCollection.cs} (74%) rename src/Qwiq.Core.Soap/{Proxies/WorkItemProxy.cs => WorkItem.cs} (76%) rename src/Qwiq.Core.Soap/{Proxies => }/WorkItemLinkInfo.cs (85%) rename src/Qwiq.Core.Soap/{Proxies => }/WorkItemLinkType.cs (94%) rename src/Qwiq.Core.Soap/{Proxies => }/WorkItemLinkTypeEnd.cs (94%) rename src/Qwiq.Core.Soap/{Proxies/WorkItemStoreProxy.cs => WorkItemStore.cs} (93%) rename src/Qwiq.Core.Soap/{Proxies => }/WorkItemType.cs (88%) diff --git a/src/Qwiq.Core.Rest/Proxies/FieldDefinition.cs b/src/Qwiq.Core.Rest/FieldDefinition.cs similarity index 90% rename from src/Qwiq.Core.Rest/Proxies/FieldDefinition.cs rename to src/Qwiq.Core.Rest/FieldDefinition.cs index 86bc07c1..706fda6b 100644 --- a/src/Qwiq.Core.Rest/Proxies/FieldDefinition.cs +++ b/src/Qwiq.Core.Rest/FieldDefinition.cs @@ -2,7 +2,7 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class FieldDefinition : Qwiq.FieldDefinition { diff --git a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs b/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs similarity index 95% rename from src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs rename to src/Qwiq.Core.Rest/FieldDefinitionCollection.cs index 2756a1b3..aab71ddc 100644 --- a/src/Qwiq.Core.Rest/Proxies/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs @@ -3,7 +3,7 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { diff --git a/src/Qwiq.Core.Rest/IdentityDescriptor.cs b/src/Qwiq.Core.Rest/IdentityDescriptor.cs new file mode 100644 index 00000000..3ed314ea --- /dev/null +++ b/src/Qwiq.Core.Rest/IdentityDescriptor.cs @@ -0,0 +1,18 @@ +using System; + +namespace Microsoft.Qwiq.Rest +{ + internal class IdentityDescriptor : IIdentityDescriptor + { + internal IdentityDescriptor(VisualStudio.Services.Identity.IdentityDescriptor descriptor) + { + if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); + Identifier = descriptor.Identifier; + IdentityType = descriptor.IdentityType; + } + + public string Identifier { get; } + + public string IdentityType { get; } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs b/src/Qwiq.Core.Rest/Node.cs similarity index 95% rename from src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs rename to src/Qwiq.Core.Rest/Node.cs index 33bc539a..ac7a2792 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemClassificationNodeProxy.cs +++ b/src/Qwiq.Core.Rest/Node.cs @@ -3,7 +3,7 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class Node : Qwiq.Node { diff --git a/src/Qwiq.Core.Rest/Proxies/Project.cs b/src/Qwiq.Core.Rest/Project.cs similarity index 97% rename from src/Qwiq.Core.Rest/Proxies/Project.cs rename to src/Qwiq.Core.Rest/Project.cs index 45d5e5de..17fdecb6 100644 --- a/src/Qwiq.Core.Rest/Proxies/Project.cs +++ b/src/Qwiq.Core.Rest/Project.cs @@ -5,11 +5,11 @@ using Microsoft.TeamFoundation.Core.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class Project : Qwiq.Project { - internal Project(TeamProjectReference project, WorkItemStoreProxy store) + internal Project(TeamProjectReference project, WorkItemStore store) : base( // REST API stores ID as GUID rather than INT // Converting from 128-bit GUID will have some loss in precision diff --git a/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs b/src/Qwiq.Core.Rest/Query.cs similarity index 93% rename from src/Qwiq.Core.Rest/Proxies/QueryProxy.cs rename to src/Qwiq.Core.Rest/Query.cs index 3c6f0fa6..86e52727 100644 --- a/src/Qwiq.Core.Rest/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -7,9 +7,9 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { - public class QueryProxy : IQuery + public class Query : IQuery { internal const int MaximumBatchSize = 200; @@ -24,7 +24,7 @@ public class QueryProxy : IQuery private readonly Wiql _query; - private readonly WorkItemStoreProxy _workItemStore; + private readonly WorkItemStore _workItemStore; private readonly DateTime? _asOf; @@ -32,19 +32,19 @@ public class QueryProxy : IQuery private static readonly Regex AsOfRegex = new Regex(@"(?asof\s)\'(?.*)\'", RegexOptions.IgnoreCase | RegexOptions.Singleline); - internal QueryProxy( + internal Query( IEnumerable ids, Wiql query, - WorkItemStoreProxy workItemStore) + WorkItemStore workItemStore) : this(query, false, workItemStore) { _ids = ids; } - internal QueryProxy( + internal Query( Wiql query, bool timePrecision, - WorkItemStoreProxy workItemStore) + WorkItemStore workItemStore) { _workItemStore = workItemStore ?? throw new ArgumentNullException(nameof(workItemStore)); @@ -125,7 +125,7 @@ public IEnumerable RunQuery() // This is done in parallel so keep performance similar to the SOAP client foreach (var workItem in Task.WhenAll(ts).GetAwaiter().GetResult().SelectMany(s => s.Select(f => f))) { - yield return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(workItem, new Lazy( + yield return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(workItem, new Lazy( () => { var proj = wits.Value[(string)workItem.Fields[CoreFieldRefNames.TeamProject]]; diff --git a/src/Qwiq.Core.Rest/Proxies/QueryFactory.cs b/src/Qwiq.Core.Rest/QueryFactory.cs similarity index 79% rename from src/Qwiq.Core.Rest/Proxies/QueryFactory.cs rename to src/Qwiq.Core.Rest/QueryFactory.cs index 32efb964..22ca463c 100644 --- a/src/Qwiq.Core.Rest/Proxies/QueryFactory.cs +++ b/src/Qwiq.Core.Rest/QueryFactory.cs @@ -4,13 +4,13 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class QueryFactory : IQueryFactory { - private readonly WorkItemStoreProxy _store; + private readonly WorkItemStore _store; - private QueryFactory(WorkItemStoreProxy store) + private QueryFactory(WorkItemStore store) { _store = store ?? throw new ArgumentNullException(nameof(store)); } @@ -18,13 +18,13 @@ private QueryFactory(WorkItemStoreProxy store) public IQuery Create(string wiql, bool dayPrecision) { return ExceptionHandlingDynamicProxyFactory.Create( - new QueryProxy(new Wiql { Query = wiql }, dayPrecision, _store)); + new Query(new Wiql { Query = wiql }, dayPrecision, _store)); } public IQuery Create(IEnumerable ids, string wiql) { return ExceptionHandlingDynamicProxyFactory.Create( - new QueryProxy(ids, new Wiql { Query = wiql }, _store)); + new Query(ids, new Wiql { Query = wiql }, _store)); } public IQuery Create(IEnumerable ids, DateTime? asOf = null) @@ -45,7 +45,7 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) return Create(ids, wiql); } - public static IQueryFactory GetInstance(WorkItemStoreProxy store) + public static IQueryFactory GetInstance(WorkItemStore store) { return new QueryFactory(store); } diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 14462c3b..12cd59da 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -95,23 +95,24 @@ + + + + + - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/Qwiq.Core.Rest/Proxies/TeamFoundationIdentityProxy.cs b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs similarity index 66% rename from src/Qwiq.Core.Rest/Proxies/TeamFoundationIdentityProxy.cs rename to src/Qwiq.Core.Rest/TeamFoundationIdentity.cs index b29d3cad..8c86d844 100644 --- a/src/Qwiq.Core.Rest/Proxies/TeamFoundationIdentityProxy.cs +++ b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs @@ -5,9 +5,9 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.VisualStudio.Services.Identity; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { - public class TeamFoundationIdentityProxy : ITeamFoundationIdentity + public class TeamFoundationIdentity : ITeamFoundationIdentity { private readonly Lazy _descriptor; @@ -15,7 +15,7 @@ public class TeamFoundationIdentityProxy : ITeamFoundationIdentity private readonly Lazy> _members; - public TeamFoundationIdentityProxy(Identity identity) + public TeamFoundationIdentity(Identity identity) { if (identity == null) throw new ArgumentNullException(nameof(identity)); DisplayName = identity.DisplayName; @@ -24,14 +24,14 @@ public TeamFoundationIdentityProxy(Identity identity) TeamFoundationId = identity.Id; UniqueUserId = identity.UniqueUserId; - _descriptor = new Lazy(()=> ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptorProxy(identity.Descriptor))); + _descriptor = new Lazy(()=> ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptor(identity.Descriptor))); _memberOf = new Lazy>( () => identity.MemberOf.Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptorProxy(item))) ?? Enumerable.Empty()); + new IdentityDescriptor(item))) ?? Enumerable.Empty()); _members = new Lazy>( @@ -39,7 +39,7 @@ public TeamFoundationIdentityProxy(Identity identity) identity.Members.Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptorProxy(item))) ?? Enumerable.Empty()); + new IdentityDescriptor(item))) ?? Enumerable.Empty()); } @@ -61,26 +61,4 @@ public TeamFoundationIdentityProxy(Identity identity) public int UniqueUserId { get; } } - - public class IdentityDescriptorProxy : IIdentityDescriptor - { - - - internal IdentityDescriptorProxy(IdentityDescriptor descriptor) - { - if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - Identifier = descriptor.Identifier; - IdentityType = descriptor.IdentityType; - } - - public string Identifier - { - get; - } - - public string IdentityType - { - get; - } - } } diff --git a/src/Qwiq.Core.Rest/Proxies/VssConnectionAdapter.cs b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs similarity index 91% rename from src/Qwiq.Core.Rest/Proxies/VssConnectionAdapter.cs rename to src/Qwiq.Core.Rest/VssConnectionAdapter.cs index 3e84d5c1..06da1597 100644 --- a/src/Qwiq.Core.Rest/Proxies/VssConnectionAdapter.cs +++ b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs @@ -3,7 +3,7 @@ using Microsoft.Qwiq.Credentials; using Microsoft.VisualStudio.Services.WebApi; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class VssConnectionAdapter : IInternalTfsTeamProjectCollection { @@ -13,7 +13,7 @@ public VssConnectionAdapter(VssConnection connection) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); AuthorizedCredentials = new TfsCredentials(connection.Credentials); - AuthorizedIdentity = new TeamFoundationIdentityProxy(_connection.AuthorizedIdentity); + AuthorizedIdentity = new TeamFoundationIdentity(_connection.AuthorizedIdentity); HasAuthenticated = connection.HasAuthenticated; Uri = connection.Uri; TimeZone = TimeZone.CurrentTimeZone; diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs b/src/Qwiq.Core.Rest/WorkItem.cs similarity index 95% rename from src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs rename to src/Qwiq.Core.Rest/WorkItem.cs index d78c39a1..3e3236d0 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { - public class WorkItemProxy : IWorkItem + internal class WorkItem : IWorkItem { - private readonly WorkItem _item; + private readonly TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem _item; private readonly Lazy _wit; - internal WorkItemProxy(WorkItem item, Lazy wit) + internal WorkItem(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, Lazy wit) { _item = item; _wit = wit; diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs b/src/Qwiq.Core.Rest/WorkItemLinkInfo.cs similarity index 91% rename from src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs rename to src/Qwiq.Core.Rest/WorkItemLinkInfo.cs index a50c5f95..54450fc9 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core.Rest/WorkItemLinkInfo.cs @@ -2,7 +2,7 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class WorkItemLinkInfo : Qwiq.WorkItemLinkInfo { diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs b/src/Qwiq.Core.Rest/WorkItemLinkType.cs similarity index 93% rename from src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs rename to src/Qwiq.Core.Rest/WorkItemLinkType.cs index c24ec963..0a8a2a1f 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkType.cs +++ b/src/Qwiq.Core.Rest/WorkItemLinkType.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class WorkItemLinkType : Qwiq.WorkItemLinkType { diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs similarity index 91% rename from src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs rename to src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs index b693046e..8e648b1d 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs @@ -2,7 +2,7 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd { diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs similarity index 94% rename from src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs rename to src/Qwiq.Core.Rest/WorkItemStore.cs index 0e745c5d..974dd3a9 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -8,9 +8,9 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { - public class WorkItemStoreProxy : IWorkItemStore + internal class WorkItemStore : IWorkItemStore { private static readonly Regex ImmutableLinkTypeNameRegex = new Regex( "(?.*)-(?.*)", @@ -30,19 +30,19 @@ public class WorkItemStoreProxy : IWorkItemStore - internal WorkItemStoreProxy( + internal WorkItemStore( Func tpcFactory, - Func queryFactory, - int batchSize = QueryProxy.MaximumBatchSize) + Func queryFactory, + int batchSize = Rest.Query.MaximumBatchSize) : this(tpcFactory, () => tpcFactory()?.GetClient(), queryFactory, batchSize) { } - internal WorkItemStoreProxy( + internal WorkItemStore( Func tpcFactory, Func wisFactory, - Func queryFactory, - int batchSize = QueryProxy.MaximumBatchSize) + Func queryFactory, + int batchSize = Rest.Query.MaximumBatchSize) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); @@ -53,8 +53,8 @@ internal WorkItemStoreProxy( // Boundary check the batch size - if (batchSize < QueryProxy.MinimumBatchSize) throw new PageSizeRangeException(); - if (batchSize > QueryProxy.MaximumBatchSize) throw new PageSizeRangeException(); + if (batchSize < Rest.Query.MinimumBatchSize) throw new PageSizeRangeException(); + if (batchSize > Rest.Query.MaximumBatchSize) throw new PageSizeRangeException(); BatchSize = batchSize; diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index 21409ab9..e74d9263 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -1,6 +1,5 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; -using Microsoft.Qwiq.Rest.Proxies; using Microsoft.TeamFoundation.Build.WebApi; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; @@ -100,7 +99,7 @@ private static VssConnection ConnectToTfsCollection(Uri endpoint, VssCredentials private static IWorkItemStore CreateRestWorkItemStore(IInternalTfsTeamProjectCollection tfs) { - return new WorkItemStoreProxy(() => tfs, QueryFactory.GetInstance); + return new WorkItemStore(() => tfs, QueryFactory.GetInstance); } // ReSharper disable ClassNeverInstantiated.Local diff --git a/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs b/src/Qwiq.Core.Rest/WorkItemType.cs similarity index 97% rename from src/Qwiq.Core.Rest/Proxies/WorkItemType.cs rename to src/Qwiq.Core.Rest/WorkItemType.cs index b6896c73..cb05ff8b 100644 --- a/src/Qwiq.Core.Rest/Proxies/WorkItemType.cs +++ b/src/Qwiq.Core.Rest/WorkItemType.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Rest.Proxies +namespace Microsoft.Qwiq.Rest { internal class WorkItemType : Qwiq.WorkItemType { diff --git a/src/Qwiq.Core.Soap/Attachment.cs b/src/Qwiq.Core.Soap/Attachment.cs new file mode 100644 index 00000000..a72f607a --- /dev/null +++ b/src/Qwiq.Core.Soap/Attachment.cs @@ -0,0 +1,38 @@ +using System; + +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap +{ + public class Attachment : IAttachment + { + private readonly Tfs.Attachment _attachment; + + internal Attachment(Tfs.Attachment attachment) + { + _attachment = attachment; + } + + public DateTime AttachedTime => DateTime.SpecifyKind(_attachment.AttachedTimeUtc, DateTimeKind.Utc); + + public string Comment + { + get => _attachment.Comment; + set => _attachment.Comment = value; + } + + public DateTime CreationTime => DateTime.SpecifyKind(_attachment.CreationTimeUtc, DateTimeKind.Utc); + + public string Extension => _attachment.Extension; + + public bool IsSaved => _attachment.IsSaved; + + public DateTime LastWriteTime => DateTime.SpecifyKind(_attachment.LastWriteTimeUtc, DateTimeKind.Utc); + + public long Length => _attachment.Length; + + public string Name => _attachment.Name; + + public Uri Uri => _attachment.Uri; + } +} diff --git a/src/Qwiq.Core.Soap/Proxies/CommonStructureServiceProxy.cs b/src/Qwiq.Core.Soap/CommonStructureService.cs similarity index 82% rename from src/Qwiq.Core.Soap/Proxies/CommonStructureServiceProxy.cs rename to src/Qwiq.Core.Soap/CommonStructureService.cs index e379fe3a..b6e33666 100644 --- a/src/Qwiq.Core.Soap/Proxies/CommonStructureServiceProxy.cs +++ b/src/Qwiq.Core.Soap/CommonStructureService.cs @@ -7,13 +7,13 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class CommonStructureServiceProxy : ICommonStructureService + public class CommonStructureService : ICommonStructureService { private readonly Tfs.ICommonStructureService4 _service; - internal CommonStructureServiceProxy(Tfs.ICommonStructureService4 service) + internal CommonStructureService(Tfs.ICommonStructureService4 service) { _service = service; } @@ -30,12 +30,12 @@ public void SetIterationDates(string nodeUri, DateTime? startDate, DateTime? fin public IProjectInfo GetProjectFromName(string projectName) { - return ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfoProxy(_service.GetProjectFromName(projectName))); + return ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfo(_service.GetProjectFromName(projectName))); } public IEnumerable ListStructures(string projectUri) { - return _service.ListStructures(projectUri).Select(i => ExceptionHandlingDynamicProxyFactory.Create(new NodeInfoProxy(i))); + return _service.ListStructures(projectUri).Select(i => ExceptionHandlingDynamicProxyFactory.Create(new NodeInfo(i))); } public XmlElement GetNodesXml(string[] nodeUris, bool childNodes) @@ -45,7 +45,7 @@ public XmlElement GetNodesXml(string[] nodeUris, bool childNodes) public IEnumerable ListAllProjects() { - return _service.ListAllProjects().Select(p => ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfoProxy(p))); + return _service.ListAllProjects().Select(p => ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfo(p))); } } } diff --git a/src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs b/src/Qwiq.Core.Soap/ExternalLink.cs similarity index 64% rename from src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs rename to src/Qwiq.Core.Soap/ExternalLink.cs index d6ce317c..7f0074fa 100644 --- a/src/Qwiq.Core.Soap/Proxies/ExternalLinkProxy.cs +++ b/src/Qwiq.Core.Soap/ExternalLink.cs @@ -1,10 +1,10 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - internal class ExternalLinkProxy : LinkProxy, IExternalLink + internal class ExternalLink : Link, IExternalLink { - internal ExternalLinkProxy(Tfs.ExternalLink externalLink) : base(externalLink) + internal ExternalLink(Tfs.ExternalLink externalLink) : base(externalLink) { LinkedArtifactUri = externalLink.LinkedArtifactUri; ArtifactLinkTypeName = externalLink.ArtifactLinkType.Name; diff --git a/src/Qwiq.Core.Soap/Proxies/FieldProxy.cs b/src/Qwiq.Core.Soap/Field.cs similarity index 88% rename from src/Qwiq.Core.Soap/Proxies/FieldProxy.cs rename to src/Qwiq.Core.Soap/Field.cs index 8cf7104f..cca3843e 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldProxy.cs +++ b/src/Qwiq.Core.Soap/Field.cs @@ -1,12 +1,12 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class FieldProxy : IField + internal class Field : IField { private readonly Tfs.Field _field; - internal FieldProxy(Tfs.Field field) + internal Field(Tfs.Field field) { _field = field; } diff --git a/src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs b/src/Qwiq.Core.Soap/FieldCollection.cs similarity index 74% rename from src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs rename to src/Qwiq.Core.Soap/FieldCollection.cs index bd45abc1..0422e2e0 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldCollectionProxy.cs +++ b/src/Qwiq.Core.Soap/FieldCollection.cs @@ -6,18 +6,18 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - internal class FieldCollectionProxy : IFieldCollection + internal class FieldCollection : IFieldCollection { private readonly Tfs.FieldCollection _innerCollection; - public FieldCollectionProxy(Tfs.FieldCollection innerCollection) + public FieldCollection(Tfs.FieldCollection innerCollection) { _innerCollection = innerCollection; } - public IField this[string name] => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection[name])); + public IField this[string name] => ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection[name])); public int Count => _innerCollection.Count; @@ -28,17 +28,17 @@ public bool Contains(string name) public IField TryGetById(int id) { - return ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection.TryGetById(id))); + return ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection.TryGetById(id))); } public IField GetById(int id) { - return ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(_innerCollection.GetById(id))); + return ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection.GetById(id))); } public IEnumerator GetEnumerator() { - return _innerCollection.Cast().Select(field => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(field))).GetEnumerator(); + return _innerCollection.Cast().Select(field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/Qwiq.Core.Soap/FieldConflict.cs b/src/Qwiq.Core.Soap/FieldConflict.cs new file mode 100644 index 00000000..50fa3a45 --- /dev/null +++ b/src/Qwiq.Core.Soap/FieldConflict.cs @@ -0,0 +1,22 @@ +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap +{ + public class FieldConflict : IFieldConflict + { + private readonly Tfs.FieldConflict _field; + + public FieldConflict(Tfs.FieldConflict field) + { + _field = field; + } + + public object BaselineValue => _field.BaselineValue; + + public string FieldReferenceName => _field.FieldReferenceName; + + public object LocalValue => _field.LocalValue; + + public object ServerValue => _field.ServerValue; + } +} diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs b/src/Qwiq.Core.Soap/FieldDefinition.cs similarity index 93% rename from src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs rename to src/Qwiq.Core.Soap/FieldDefinition.cs index a8dede32..4ac571b9 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldDefinition.cs +++ b/src/Qwiq.Core.Soap/FieldDefinition.cs @@ -2,7 +2,7 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class FieldDefinition : Qwiq.FieldDefinition { diff --git a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs similarity index 97% rename from src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs rename to src/Qwiq.Core.Soap/FieldDefinitionCollection.cs index 3a0a2ec4..1831712e 100644 --- a/src/Qwiq.Core.Soap/Proxies/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs @@ -2,9 +2,10 @@ using System.Linq; using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { diff --git a/src/Qwiq.Core.Soap/Hyperlink.cs b/src/Qwiq.Core.Soap/Hyperlink.cs new file mode 100644 index 00000000..eface8a2 --- /dev/null +++ b/src/Qwiq.Core.Soap/Hyperlink.cs @@ -0,0 +1,17 @@ +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap +{ + public class Hyperlink : Link, IHyperlink + { + private readonly Tfs.Hyperlink _hyperLink; + + internal Hyperlink(Tfs.Hyperlink hyperLink) : base(hyperLink) + { + _hyperLink = hyperLink; + } + + public string Location => _hyperLink.Location; + } +} + diff --git a/src/Qwiq.Core.Soap/IdentityDescriptor.cs b/src/Qwiq.Core.Soap/IdentityDescriptor.cs new file mode 100644 index 00000000..258f13b7 --- /dev/null +++ b/src/Qwiq.Core.Soap/IdentityDescriptor.cs @@ -0,0 +1,18 @@ +using Tfs = Microsoft.TeamFoundation.Framework.Client; + +namespace Microsoft.Qwiq.Soap +{ + public class IdentityDescriptor : IIdentityDescriptor + { + private readonly Tfs.IdentityDescriptor _descriptor; + + internal IdentityDescriptor(Tfs.IdentityDescriptor descriptor) + { + _descriptor = descriptor; + } + + public string Identifier => _descriptor.Identifier; + + public string IdentityType => _descriptor.IdentityType; + } +} diff --git a/src/Qwiq.Core.Soap/Proxies/IdentityManagementServiceProxy.cs b/src/Qwiq.Core.Soap/IdentityManagementService.cs similarity index 85% rename from src/Qwiq.Core.Soap/Proxies/IdentityManagementServiceProxy.cs rename to src/Qwiq.Core.Soap/IdentityManagementService.cs index cf95c952..0c736922 100644 --- a/src/Qwiq.Core.Soap/Proxies/IdentityManagementServiceProxy.cs +++ b/src/Qwiq.Core.Soap/IdentityManagementService.cs @@ -6,13 +6,13 @@ using Tfs = Microsoft.TeamFoundation.Framework; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class IdentityManagementServiceProxy : IIdentityManagementService + public class IdentityManagementService : IIdentityManagementService { private readonly Tfs.Client.IIdentityManagementService2 _identityManagementService2; - internal IdentityManagementServiceProxy(Tfs.Client.IIdentityManagementService2 identityManagementService2) + internal IdentityManagementService(Tfs.Client.IIdentityManagementService2 identityManagementService2) { _identityManagementService2 = identityManagementService2; } @@ -25,7 +25,7 @@ public IEnumerable ReadIdentities(ICollection identity == null ? null : ExceptionHandlingDynamicProxyFactory.Create(new TeamFoundationIdentityProxy(identity))); + return identities.Select(identity => identity == null ? null : ExceptionHandlingDynamicProxyFactory.Create(new TeamFoundationIdentity(identity))); } public IEnumerable>> ReadIdentities(IdentitySearchFactor searchFactor, ICollection searchFactorValues) @@ -49,7 +49,7 @@ public IEnumerable>> R public IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier) { - return ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptorProxy(new Tfs.Client.IdentityDescriptor(identityType, identifier))); + return ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptor(new Tfs.Client.IdentityDescriptor(identityType, identifier))); } private ITeamFoundationIdentity TryCreateProxy(Tfs.Client.TeamFoundationIdentity identity) @@ -61,7 +61,7 @@ private ITeamFoundationIdentity TryCreateProxy(Tfs.Client.TeamFoundationIdentity return ExceptionHandlingDynamicProxyFactory.Create( - new TeamFoundationIdentityProxy(identity)); + new TeamFoundationIdentity(identity)); } } } diff --git a/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs b/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs index cf991e09..c16071aa 100644 --- a/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs +++ b/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs @@ -3,7 +3,6 @@ using System.Linq; using Microsoft.Qwiq.Exceptions; -using Microsoft.Qwiq.Soap.Proxies; using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -20,7 +19,7 @@ internal ItemAlreadyUpdatedOnServerException(Tfs.ItemAlreadyUpdatedOnServerExcep public IEnumerable FieldConflicts { - get { return _exception.FieldConflicts.Select(item => ExceptionHandlingDynamicProxyFactory.Create(new FieldConflictProxy(item))); } + get { return _exception.FieldConflicts.Select(item => ExceptionHandlingDynamicProxyFactory.Create(new FieldConflict(item))); } } } } diff --git a/src/Qwiq.Core.Soap/Link.cs b/src/Qwiq.Core.Soap/Link.cs new file mode 100644 index 00000000..defc1ea8 --- /dev/null +++ b/src/Qwiq.Core.Soap/Link.cs @@ -0,0 +1,19 @@ +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap +{ + public class Link : ILink + { + private readonly Tfs.Link _link; + + internal Link(Tfs.Link link) + { + _link = link; + } + + public string Comment => _link.Comment; + + public BaseLinkType BaseType => (BaseLinkType) _link.BaseType; + } +} + diff --git a/src/Qwiq.Core.Soap/Proxies/LinkCollectionProxy.cs b/src/Qwiq.Core.Soap/LinkCollection.cs similarity index 83% rename from src/Qwiq.Core.Soap/Proxies/LinkCollectionProxy.cs rename to src/Qwiq.Core.Soap/LinkCollection.cs index 88e3f241..451dda37 100644 --- a/src/Qwiq.Core.Soap/Proxies/LinkCollectionProxy.cs +++ b/src/Qwiq.Core.Soap/LinkCollection.cs @@ -4,15 +4,15 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class LinkCollectionProxy : ICollection + internal class LinkCollection : ICollection { private readonly Tfs.WorkItem _item; private readonly LinkHelper _linkHelper; private readonly LinkMapper _linkMapper; - internal LinkCollectionProxy(Tfs.WorkItem item) + internal LinkCollection(Tfs.WorkItem item) { _item = item; _linkHelper = new LinkHelper(); @@ -69,15 +69,9 @@ public bool Remove(ILink item) return wasFound; } - public int Count - { - get { return _item.Links.Count; } - } + public int Count => _item.Links.Count; - public bool IsReadOnly - { - get { return ((IList) _item.Links).IsReadOnly; } - } + public bool IsReadOnly => ((IList) _item.Links).IsReadOnly; } } diff --git a/src/Qwiq.Core.Soap/LinkMapper.cs b/src/Qwiq.Core.Soap/LinkMapper.cs index 0e42f3f5..4c459851 100644 --- a/src/Qwiq.Core.Soap/LinkMapper.cs +++ b/src/Qwiq.Core.Soap/LinkMapper.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Qwiq.Exceptions; -using Microsoft.Qwiq.Soap.Proxies; using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -38,15 +37,15 @@ public ILink Map(Tfs.Link link) { case Tfs.BaseLinkType.RelatedLink: var relatedLink = (Tfs.RelatedLink)link; - return ExceptionHandlingDynamicProxyFactory.Create(new RelatedLinkProxy(relatedLink)); + return ExceptionHandlingDynamicProxyFactory.Create(new RelatedLink(relatedLink)); case Tfs.BaseLinkType.Hyperlink: var hyperlink = (Tfs.Hyperlink)link; - return ExceptionHandlingDynamicProxyFactory.Create(new HyperlinkProxy(hyperlink)); + return ExceptionHandlingDynamicProxyFactory.Create(new Hyperlink(hyperlink)); case Tfs.BaseLinkType.ExternalLink: var externalLink = (Tfs.ExternalLink)link; - return ExceptionHandlingDynamicProxyFactory.Create(new ExternalLinkProxy(externalLink)); + return ExceptionHandlingDynamicProxyFactory.Create(new ExternalLink(externalLink)); default: throw new ArgumentException("Unknown link type", nameof(link)); diff --git a/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs b/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs index f5611de9..ba1dceaf 100644 --- a/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs +++ b/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.Soap { internal static class LinkTypeEndMapper { - public static TeamFoundation.WorkItemTracking.Client.WorkItemLinkTypeEnd Map(WorkItemStore store, IWorkItemLinkTypeEnd end) + public static TeamFoundation.WorkItemTracking.Client.WorkItemLinkTypeEnd Map(TeamFoundation.WorkItemTracking.Client.WorkItemStore store, IWorkItemLinkTypeEnd end) { var linkType = store.WorkItemLinkTypes.Single(type => type.ReferenceName == end.LinkType.ReferenceName); return end.IsForwardLink ? linkType.ForwardEnd : linkType.ReverseEnd; diff --git a/src/Qwiq.Core.Soap/Proxies/Node.cs b/src/Qwiq.Core.Soap/Node.cs similarity index 94% rename from src/Qwiq.Core.Soap/Proxies/Node.cs rename to src/Qwiq.Core.Soap/Node.cs index c77bf120..687fd97d 100644 --- a/src/Qwiq.Core.Soap/Proxies/Node.cs +++ b/src/Qwiq.Core.Soap/Node.cs @@ -2,7 +2,7 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class Node : Qwiq.Node { diff --git a/src/Qwiq.Core.Soap/NodeInfo.cs b/src/Qwiq.Core.Soap/NodeInfo.cs new file mode 100644 index 00000000..a1bc94d6 --- /dev/null +++ b/src/Qwiq.Core.Soap/NodeInfo.cs @@ -0,0 +1,21 @@ +using Tfs = Microsoft.TeamFoundation.Server; + +namespace Microsoft.Qwiq.Soap +{ + internal class NodeInfo : INodeInfo + { + private readonly Tfs.NodeInfo _nodeInfo; + + internal NodeInfo(Tfs.NodeInfo nodeInfo) + { + _nodeInfo = nodeInfo; + } + + public string Uri + { + get => _nodeInfo.Uri; + set => _nodeInfo.Uri = value; + } + } +} + diff --git a/src/Qwiq.Core.Soap/Proxies/Project.cs b/src/Qwiq.Core.Soap/Project.cs similarity index 92% rename from src/Qwiq.Core.Soap/Proxies/Project.cs rename to src/Qwiq.Core.Soap/Project.cs index 26a8d60a..f4ce315a 100644 --- a/src/Qwiq.Core.Soap/Proxies/Project.cs +++ b/src/Qwiq.Core.Soap/Project.cs @@ -6,11 +6,11 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class Project : Qwiq.Project { - internal Project(Tfs.Project project, IWorkItemStore store) + internal Project(Tfs.Project project) : base( project.Id, project.Guid, diff --git a/src/Qwiq.Core.Soap/ProjectInfo.cs b/src/Qwiq.Core.Soap/ProjectInfo.cs new file mode 100644 index 00000000..0f7b4539 --- /dev/null +++ b/src/Qwiq.Core.Soap/ProjectInfo.cs @@ -0,0 +1,19 @@ +using Tfs = Microsoft.TeamFoundation.Server; + +namespace Microsoft.Qwiq.Soap +{ + internal class ProjectInfo : IProjectInfo + { + private readonly Tfs.ProjectInfo _projectInfo; + + internal ProjectInfo(Tfs.ProjectInfo projectInfo) + { + _projectInfo = projectInfo; + } + + public string Uri { get => _projectInfo.Uri; + set => _projectInfo.Uri = value; + } + } +} + diff --git a/src/Qwiq.Core.Soap/Proxies/ProjectPropertyProxy.cs b/src/Qwiq.Core.Soap/ProjectProperty.cs similarity index 53% rename from src/Qwiq.Core.Soap/Proxies/ProjectPropertyProxy.cs rename to src/Qwiq.Core.Soap/ProjectProperty.cs index 9bf96807..3b6dc793 100644 --- a/src/Qwiq.Core.Soap/Proxies/ProjectPropertyProxy.cs +++ b/src/Qwiq.Core.Soap/ProjectProperty.cs @@ -1,12 +1,12 @@ using Tfs = Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class ProjectPropertyProxy : IProjectProperty + internal class ProjectProperty : IProjectProperty { private readonly Tfs.ProjectProperty _projectProperty; - internal ProjectPropertyProxy(Tfs.ProjectProperty projectProperty) + internal ProjectProperty(Tfs.ProjectProperty projectProperty) { _projectProperty = projectProperty; } diff --git a/src/Qwiq.Core.Soap/Proxies/AttachmentProxy.cs b/src/Qwiq.Core.Soap/Proxies/AttachmentProxy.cs deleted file mode 100644 index 1f593e55..00000000 --- a/src/Qwiq.Core.Soap/Proxies/AttachmentProxy.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; - -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class AttachmentProxy : IAttachment - { - private readonly Tfs.Attachment _attachment; - - internal AttachmentProxy(Tfs.Attachment attachment) - { - _attachment = attachment; - } - - public DateTime AttachedTime - { - get { return DateTime.SpecifyKind(_attachment.AttachedTimeUtc, DateTimeKind.Utc); } - } - - public string Comment - { - get { return _attachment.Comment; } - set { _attachment.Comment = value; } - } - - public DateTime CreationTime - { - get { return DateTime.SpecifyKind(_attachment.CreationTimeUtc, DateTimeKind.Utc); } - } - - public string Extension - { - get { return _attachment.Extension; } - } - - public bool IsSaved - { - get { return _attachment.IsSaved; } - } - - public DateTime LastWriteTime - { - get { return DateTime.SpecifyKind(_attachment.LastWriteTimeUtc, DateTimeKind.Utc); } - } - - public long Length - { - get { return _attachment.Length; } - } - - public string Name - { - get { return _attachment.Name; } - } - - public Uri Uri - { - get { return _attachment.Uri; } - } - } -} diff --git a/src/Qwiq.Core.Soap/Proxies/FieldConflictProxy.cs b/src/Qwiq.Core.Soap/Proxies/FieldConflictProxy.cs deleted file mode 100644 index 5b2749b3..00000000 --- a/src/Qwiq.Core.Soap/Proxies/FieldConflictProxy.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class FieldConflictProxy : IFieldConflict - { - private readonly Tfs.FieldConflict _field; - - public FieldConflictProxy(Tfs.FieldConflict field) - { - _field = field; - } - - public object BaselineValue - { - get { return _field.BaselineValue; } - } - - public string FieldReferenceName - { - get { return _field.FieldReferenceName; } - } - - public object LocalValue - { - get { return _field.LocalValue; } - } - - public object ServerValue - { - get { return _field.ServerValue; } - } - } -} diff --git a/src/Qwiq.Core.Soap/Proxies/HyperlinkProxy.cs b/src/Qwiq.Core.Soap/Proxies/HyperlinkProxy.cs deleted file mode 100644 index 174a1d3c..00000000 --- a/src/Qwiq.Core.Soap/Proxies/HyperlinkProxy.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class HyperlinkProxy : LinkProxy, IHyperlink - { - private readonly Tfs.Hyperlink _hyperLink; - - internal HyperlinkProxy(Tfs.Hyperlink hyperLink) : base(hyperLink) - { - _hyperLink = hyperLink; - } - - public string Location - { - get { return _hyperLink.Location; } - } - } -} - diff --git a/src/Qwiq.Core.Soap/Proxies/IdentityDescriptorProxy.cs b/src/Qwiq.Core.Soap/Proxies/IdentityDescriptorProxy.cs deleted file mode 100644 index 0c0ad980..00000000 --- a/src/Qwiq.Core.Soap/Proxies/IdentityDescriptorProxy.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Tfs = Microsoft.TeamFoundation.Framework.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class IdentityDescriptorProxy : IIdentityDescriptor - { - private readonly Tfs.IdentityDescriptor _descriptor; - - internal IdentityDescriptorProxy(Tfs.IdentityDescriptor descriptor) - { - _descriptor = descriptor; - } - - public string Identifier - { - get { return _descriptor.Identifier; } - } - - public string IdentityType - { - get { return _descriptor.IdentityType; } - } - } -} diff --git a/src/Qwiq.Core.Soap/Proxies/LinkProxy.cs b/src/Qwiq.Core.Soap/Proxies/LinkProxy.cs deleted file mode 100644 index ed62f202..00000000 --- a/src/Qwiq.Core.Soap/Proxies/LinkProxy.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class LinkProxy : ILink - { - private readonly Tfs.Link _link; - - internal LinkProxy(Tfs.Link link) - { - _link = link; - } - - public string Comment - { - get { return _link.Comment; } - } - - public BaseLinkType BaseType - { - get { return (BaseLinkType) _link.BaseType; } - } - } -} - diff --git a/src/Qwiq.Core.Soap/Proxies/NodeInfoProxy.cs b/src/Qwiq.Core.Soap/Proxies/NodeInfoProxy.cs deleted file mode 100644 index dd79b20f..00000000 --- a/src/Qwiq.Core.Soap/Proxies/NodeInfoProxy.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Tfs = Microsoft.TeamFoundation.Server; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class NodeInfoProxy : INodeInfo - { - private readonly Tfs.NodeInfo _nodeInfo; - - internal NodeInfoProxy(Tfs.NodeInfo nodeInfo) - { - _nodeInfo = nodeInfo; - } - - public string Uri - { - get { return _nodeInfo.Uri; } - set { _nodeInfo.Uri = value; } - } - } -} - diff --git a/src/Qwiq.Core.Soap/Proxies/ProjectInfoProxy.cs b/src/Qwiq.Core.Soap/Proxies/ProjectInfoProxy.cs deleted file mode 100644 index ff7795b4..00000000 --- a/src/Qwiq.Core.Soap/Proxies/ProjectInfoProxy.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Tfs = Microsoft.TeamFoundation.Server; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class ProjectInfoProxy : IProjectInfo - { - private readonly Tfs.ProjectInfo _projectInfo; - - internal ProjectInfoProxy(Tfs.ProjectInfo projectInfo) - { - _projectInfo = projectInfo; - } - - public string Uri { get { return _projectInfo.Uri; } set { _projectInfo.Uri = value; } } - } -} - diff --git a/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs b/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs deleted file mode 100644 index 5084bd9c..00000000 --- a/src/Qwiq.Core.Soap/Proxies/RelatedLinkProxy.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Qwiq.Exceptions; - -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - -namespace Microsoft.Qwiq.Soap.Proxies -{ - public class RelatedLinkProxy : LinkProxy, IRelatedLink - { - private readonly Tfs.RelatedLink _relatedLink; - - internal RelatedLinkProxy(Tfs.RelatedLink relatedLink) : base(relatedLink) - { - _relatedLink = relatedLink; - } - - public int RelatedWorkItemId - { - get { return _relatedLink.RelatedWorkItemId; } - } - - public IWorkItemLinkTypeEnd LinkTypeEnd - { - get - { - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeEnd(_relatedLink.LinkTypeEnd)); - } - } - - public string LinkSubType - { - get { return _relatedLink.LinkTypeEnd.Name; } - } - } -} - diff --git a/src/Qwiq.Core.Soap/Proxies/QueryProxy.cs b/src/Qwiq.Core.Soap/Query.cs similarity index 80% rename from src/Qwiq.Core.Soap/Proxies/QueryProxy.cs rename to src/Qwiq.Core.Soap/Query.cs index 1489d7f7..31d192a3 100644 --- a/src/Qwiq.Core.Soap/Proxies/QueryProxy.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -3,13 +3,13 @@ using Microsoft.Qwiq.Exceptions; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class QueryProxy : IQuery + internal class Query : IQuery { private readonly TeamFoundation.WorkItemTracking.Client.Query _query; - internal QueryProxy(TeamFoundation.WorkItemTracking.Client.Query query) + internal Query(TeamFoundation.WorkItemTracking.Client.Query query) { _query = query; } @@ -18,7 +18,7 @@ public IEnumerable RunQuery() { return _query.RunQuery() .Cast() - .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(item))); + .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(item))); } public IEnumerable RunLinkQuery() diff --git a/src/Qwiq.Core.Soap/Proxies/QueryFactory.cs b/src/Qwiq.Core.Soap/QueryFactory.cs similarity index 70% rename from src/Qwiq.Core.Soap/Proxies/QueryFactory.cs rename to src/Qwiq.Core.Soap/QueryFactory.cs index 382fc394..acfbc292 100644 --- a/src/Qwiq.Core.Soap/Proxies/QueryFactory.cs +++ b/src/Qwiq.Core.Soap/QueryFactory.cs @@ -5,18 +5,18 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class QueryFactory : IQueryFactory { - private readonly WorkItemStore _store; + private readonly TeamFoundation.WorkItemTracking.Client.WorkItemStore _store; - private QueryFactory(WorkItemStore store) + private QueryFactory(TeamFoundation.WorkItemTracking.Client.WorkItemStore store) { _store = store; } - public static IQueryFactory GetInstance(WorkItemStore store) + public static IQueryFactory GetInstance(TeamFoundation.WorkItemTracking.Client.WorkItemStore store) { return new QueryFactory(store); } @@ -24,7 +24,7 @@ public static IQueryFactory GetInstance(WorkItemStore store) public IQuery Create(string wiql, bool dayPrecision) { return ExceptionHandlingDynamicProxyFactory.Create( - new QueryProxy(new Query(_store, wiql, null, dayPrecision))); + new Query(new TeamFoundation.WorkItemTracking.Client.Query(_store, wiql, null, dayPrecision))); } public IQuery Create(IEnumerable ids, string wiql) @@ -32,7 +32,7 @@ public IQuery Create(IEnumerable ids, string wiql) if (ids == null) throw new ArgumentNullException(nameof(ids)); if (string.IsNullOrWhiteSpace(wiql)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(wiql)); - return ExceptionHandlingDynamicProxyFactory.Create(new QueryProxy(new Query(_store, wiql, ids.ToArray()))); + return ExceptionHandlingDynamicProxyFactory.Create(new Query(new TeamFoundation.WorkItemTracking.Client.Query(_store, wiql, ids.ToArray()))); } public IQuery Create(IEnumerable ids, DateTime? asOf = null) diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index a987f592..9438bb34 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -189,46 +189,46 @@ + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/Qwiq.Core.Soap/RelatedLink.cs b/src/Qwiq.Core.Soap/RelatedLink.cs new file mode 100644 index 00000000..0ac3d989 --- /dev/null +++ b/src/Qwiq.Core.Soap/RelatedLink.cs @@ -0,0 +1,23 @@ +using Microsoft.Qwiq.Exceptions; + +using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Microsoft.Qwiq.Soap +{ + public class RelatedLink : Link, IRelatedLink + { + private readonly Tfs.RelatedLink _relatedLink; + + internal RelatedLink(Tfs.RelatedLink relatedLink) : base(relatedLink) + { + _relatedLink = relatedLink; + } + + public int RelatedWorkItemId => _relatedLink.RelatedWorkItemId; + + public IWorkItemLinkTypeEnd LinkTypeEnd => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeEnd(_relatedLink.LinkTypeEnd)); + + public string LinkSubType => _relatedLink.LinkTypeEnd.Name; + } +} + diff --git a/src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs b/src/Qwiq.Core.Soap/Revision.cs similarity index 75% rename from src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs rename to src/Qwiq.Core.Soap/Revision.cs index 2c64aec5..3ecf563f 100644 --- a/src/Qwiq.Core.Soap/Proxies/RevisionProxy.cs +++ b/src/Qwiq.Core.Soap/Revision.cs @@ -5,16 +5,16 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { /// /// Wrapper around the TFS RevisionProxy. This exists so that every agent doesn't need to reference /// all the TFS libraries. /// - public class RevisionProxy : IRevision + public class Revision : IRevision { private readonly Tfs.Revision _rev; - internal RevisionProxy(Tfs.Revision revision) + internal Revision(Tfs.Revision revision) { _rev = revision; } @@ -24,7 +24,7 @@ internal RevisionProxy(Tfs.Revision revision) /// public IEnumerable Attachments { - get { return _rev.Attachments.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new AttachmentProxy(item))); } + get { return _rev.Attachments.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Attachment(item))); } } /// @@ -34,7 +34,7 @@ public IDictionary Fields { get { - return _rev.Fields.Cast().ToDictionary(field => field.Name, field => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(field))); + return _rev.Fields.Cast().ToDictionary(field => field.Name, field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))); } } @@ -43,36 +43,27 @@ public IDictionary Fields /// /// Gets the index of this revision. /// - public int Index - { - get { return _rev.Index; } - } + public int Index => _rev.Index; /// /// Gets the links of the work item in this revision. /// public IEnumerable Links { - get { return _rev.Links.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new LinkProxy(item))); } + get { return _rev.Links.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Link(item))); } } /// /// Gets the work item that is stored in this revision. /// - public IWorkItem WorkItem - { - get { return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(_rev.WorkItem)); } - } + public IWorkItem WorkItem => ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_rev.WorkItem)); /// /// Gets the value of the specified field in the work item of this revision. /// /// The field of interest in the work item of this revision. /// The value of the specified field. - public object this[string name] - { - get { return _rev[name]; } - } + public object this[string name] => _rev[name]; /// /// Gets the tagline for this revision. diff --git a/src/Qwiq.Core.Soap/Proxies/TeamFoundationIdentityProxy.cs b/src/Qwiq.Core.Soap/TeamFoundationIdentity.cs similarity index 79% rename from src/Qwiq.Core.Soap/Proxies/TeamFoundationIdentityProxy.cs rename to src/Qwiq.Core.Soap/TeamFoundationIdentity.cs index 929a4f08..7bf83d55 100644 --- a/src/Qwiq.Core.Soap/Proxies/TeamFoundationIdentityProxy.cs +++ b/src/Qwiq.Core.Soap/TeamFoundationIdentity.cs @@ -6,9 +6,9 @@ using Tfs = Microsoft.TeamFoundation.Framework.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class TeamFoundationIdentityProxy : ITeamFoundationIdentity + public class TeamFoundationIdentity : ITeamFoundationIdentity { private readonly Tfs.TeamFoundationIdentity _identity; @@ -18,14 +18,14 @@ public class TeamFoundationIdentityProxy : ITeamFoundationIdentity private readonly Lazy> _members; - internal TeamFoundationIdentityProxy(Tfs.TeamFoundationIdentity identity) + internal TeamFoundationIdentity(Tfs.TeamFoundationIdentity identity) { _identity = identity; _descriptor = new Lazy( () => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptorProxy(_identity?.Descriptor))); + new IdentityDescriptor(_identity?.Descriptor))); _memberOf = new Lazy>( @@ -33,7 +33,7 @@ internal TeamFoundationIdentityProxy(Tfs.TeamFoundationIdentity identity) _identity?.MemberOf.Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptorProxy(item))) ?? Enumerable.Empty()); + new IdentityDescriptor(item))) ?? Enumerable.Empty()); _members = new Lazy>( @@ -41,7 +41,7 @@ internal TeamFoundationIdentityProxy(Tfs.TeamFoundationIdentity identity) _identity?.Members.Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptorProxy(item))) ?? Enumerable.Empty()); + new IdentityDescriptor(item))) ?? Enumerable.Empty()); } public IIdentityDescriptor Descriptor => _descriptor.Value; diff --git a/src/Qwiq.Core.Soap/Proxies/TfsTeamProjectCollectionProxy.cs b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs similarity index 74% rename from src/Qwiq.Core.Soap/Proxies/TfsTeamProjectCollectionProxy.cs rename to src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs index 38ee5fcd..9fad5957 100644 --- a/src/Qwiq.Core.Soap/Proxies/TfsTeamProjectCollectionProxy.cs +++ b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs @@ -2,35 +2,34 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.TeamFoundation.Server; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { - public class TfsTeamProjectCollectionProxy : IInternalTfsTeamProjectCollection + internal class TfsTeamProjectCollection : IInternalTfsTeamProjectCollection { private readonly Lazy _css; private readonly Lazy _ims; - private readonly TfsTeamProjectCollection _tfs; + private readonly TeamFoundation.Client.TfsTeamProjectCollection _tfs; - internal TfsTeamProjectCollectionProxy(TfsTeamProjectCollection tfs) + internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection tfs) { _tfs = tfs; _css = new Lazy( () => ExceptionHandlingDynamicProxyFactory.Create( - new CommonStructureServiceProxy(_tfs?.GetService()))); + new CommonStructureService(_tfs?.GetService()))); _ims = new Lazy( () => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityManagementServiceProxy(_tfs?.GetService()))); + new IdentityManagementService(_tfs?.GetService()))); } public TfsCredentials AuthorizedCredentials => new TfsCredentials(_tfs?.ClientCredentials); - public ITeamFoundationIdentity AuthorizedIdentity => new TeamFoundationIdentityProxy(_tfs?.AuthorizedIdentity); + public ITeamFoundationIdentity AuthorizedIdentity => new TeamFoundationIdentity(_tfs?.AuthorizedIdentity); public ICommonStructureService CommonStructureService => _css.Value; diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs b/src/Qwiq.Core.Soap/WorkItem.cs similarity index 76% rename from src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs rename to src/Qwiq.Core.Soap/WorkItem.cs index 3c724d6e..d2d1eb77 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemProxy.cs +++ b/src/Qwiq.Core.Soap/WorkItem.cs @@ -6,25 +6,25 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { /// /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference /// all the TFS libraries. /// - public class WorkItemProxy : IWorkItem + internal class WorkItem : IWorkItem { private readonly Tfs.WorkItem _item; - internal WorkItemProxy(Tfs.WorkItem item) + internal WorkItem(Tfs.WorkItem item) { _item = item; } public string AssignedTo { - get { return _item[Tfs.CoreFieldReferenceNames.AssignedTo].ToString(); } - set { _item[Tfs.CoreFieldReferenceNames.AssignedTo] = value; } + get => _item[Tfs.CoreFieldReferenceNames.AssignedTo].ToString(); + set => _item[Tfs.CoreFieldReferenceNames.AssignedTo] = value; } /// @@ -32,17 +32,14 @@ public string AssignedTo /// public string AreaPath { - get { return _item.AreaPath; } - set { _item.AreaPath = value; } + get => _item.AreaPath; + set => _item.AreaPath = value; } /// /// Gets the number of attached files for this work item. /// - public int AttachedFileCount - { - get { return _item.AttachedFileCount; } - } + public int AttachedFileCount => _item.AttachedFileCount; /// /// Gets the Microsoft.TeamFoundation.WorkItemTracking.Client.AttachmentCollection @@ -50,42 +47,30 @@ public int AttachedFileCount /// public IEnumerable Attachments { - get { return _item.Attachments.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new AttachmentProxy(item))); } + get { return _item.Attachments.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Attachment(item))); } } /// /// Gets the string value of the ChangedBy field for this work item. /// - public string ChangedBy - { - get { return _item.ChangedBy; } - } + public string ChangedBy => _item.ChangedBy; /// /// Gets the System.DateTime object that represents the date and time that this /// work item was last changed. /// - public DateTime ChangedDate - { - get { return _item.ChangedDate; } - } + public DateTime ChangedDate => _item.ChangedDate; /// /// Gets the string value of the CreatedBy field for this work item. /// - public string CreatedBy - { - get { return _item.CreatedBy; } - } + public string CreatedBy => _item.CreatedBy; /// /// Gets the System.DateTime object that represents the date and time that this /// work item was created. /// - public DateTime CreatedDate - { - get { return _item.CreatedDate; } - } + public DateTime CreatedDate => _item.CreatedDate; /// /// Closes this WorkItem instance and frees memory that is associated with it. @@ -100,55 +85,43 @@ public void Close() /// public string Description { - get { return _item.Description; } - set { _item.Description = value; } + get => _item.Description; + set => _item.Description = value; } /// /// Gets the number of external links in this work item. /// - public int ExternalLinkCount - { - get { return _item.ExternalLinkCount; } - } + public int ExternalLinkCount => _item.ExternalLinkCount; - public IFieldCollection Fields - { - get { return ExceptionHandlingDynamicProxyFactory.Create(new FieldCollectionProxy(_item.Fields)); } - } + public IFieldCollection Fields => ExceptionHandlingDynamicProxyFactory.Create(new FieldCollection(_item.Fields)); /// /// Gets or sets the string value of the History field for this work item. /// public string History { - get { return _item.History; } - set { _item.History = value; } + get => _item.History; + set => _item.History = value; } /// /// Gets the number of hyperlinks in this work item. /// - public int HyperLinkCount - { - get { return _item.HyperLinkCount; } - } + public int HyperLinkCount => _item.HyperLinkCount; /// /// Gets the ID of this work item. /// - public int Id - { - get { return _item.Id; } - } + public int Id => _item.Id; /// /// Gets or sets the string value of the IterationPath field of this work item. /// public string IterationPath { - get { return _item.IterationPath; } - set { _item.IterationPath = value; } + get => _item.IterationPath; + set => _item.IterationPath = value; } /// @@ -157,35 +130,23 @@ public string IterationPath /// /// Gets the links of the work item in this revision. /// - public ICollection Links - { - get { return ExceptionHandlingDynamicProxyFactory.Create>(new LinkCollectionProxy(_item)); } - } + public ICollection Links => ExceptionHandlingDynamicProxyFactory.Create>(new LinkCollection(_item)); /// /// Gets the number of related links of this work item. /// - public int RelatedLinkCount - { - get { return _item.RelatedLinkCount; } - } + public int RelatedLinkCount => _item.RelatedLinkCount; /// /// Gets a System.DateTime object that represents the revision date and time /// of this work item. /// - public DateTime RevisedDate - { - get { return _item.RevisedDate; } - } + public DateTime RevisedDate => _item.RevisedDate; /// /// Gets the integer that represents the revision number of this work item. /// - public long Revision - { - get { return _item.Revision; } - } + public long Revision => _item.Revision; /// /// Gets an object that represents a collection of valid revision numbers for this work @@ -193,7 +154,7 @@ public long Revision /// public IEnumerable Revisions { - get { return _item.Revisions.Cast().Select(r => ExceptionHandlingDynamicProxyFactory.Create(new RevisionProxy(r))); } + get { return _item.Revisions.Cast().Select(r => ExceptionHandlingDynamicProxyFactory.Create(new Revision(r))); } } /// @@ -201,20 +162,20 @@ public IEnumerable Revisions /// public string State { - get { return _item.State; } - set { _item.State = value; } + get => _item.State; + set => _item.State = value; } public string Tags { - get { return _item.Tags; } - set { _item["Tags"] = value; } + get => _item.Tags; + set => _item["Tags"] = value; } public string Keywords { - get { return (string)_item[WorkItemFields.Keywords]; } - set { _item[WorkItemFields.Keywords] = value; } + get => (string)_item[WorkItemFields.Keywords]; + set => _item[WorkItemFields.Keywords] = value; } /// @@ -222,8 +183,8 @@ public string Keywords /// public string Title { - get { return _item.Title; } - set { _item.Title = value; } + get => _item.Title; + set => _item.Title = value; } /// @@ -233,33 +194,24 @@ public string Title /// /// The Type property is null. /// - public IWorkItemType Type - { - get { return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(_item.Type)); } - } + public IWorkItemType Type => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(_item.Type)); /// /// Gets the uniform resource identifier (System.Uri) of this work item. /// - public Uri Uri - { - get { return _item.Uri; } - } + public Uri Uri => _item.Uri; - public long Rev - { - get { return _item.Rev; } - } + public long Rev => _item.Rev; public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) { var rawLinkTypeEnd = LinkTypeEndMapper.Map(_item.Store, linkTypeEnd); - return ExceptionHandlingDynamicProxyFactory.Create(new RelatedLinkProxy(new Tfs.RelatedLink(rawLinkTypeEnd, relatedWorkItem.Id))); + return ExceptionHandlingDynamicProxyFactory.Create(new RelatedLink(new Tfs.RelatedLink(rawLinkTypeEnd, relatedWorkItem.Id))); } public IHyperlink CreateHyperlink(string location) { - return ExceptionHandlingDynamicProxyFactory.Create(new HyperlinkProxy(new Tfs.Hyperlink(location))); + return ExceptionHandlingDynamicProxyFactory.Create(new Hyperlink(new Tfs.Hyperlink(location))); } /// @@ -275,8 +227,8 @@ public IHyperlink CreateHyperlink(string location) /// public object this[string name] { - get { return _item[name]; } - set { _item[name] = value; } + get => _item[name]; + set => _item[name] = value; } /// @@ -285,7 +237,7 @@ public object this[string name] /// A new WorkItem instance that is a copy of this WorkItem instance. public IWorkItem Copy() { - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(_item.Copy())); + return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy())); } /// @@ -302,7 +254,7 @@ public IWorkItem Copy() public IWorkItem Copy(IWorkItemType targetType) { var type = GetWorkItemType(targetType); - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(_item.Copy(type))); + return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy(type))); } private Tfs.WorkItemType GetWorkItemType(IWorkItemType type) @@ -327,7 +279,7 @@ public IWorkItem Copy(IWorkItemType targetType, WorkItemCopyFlags flags) { var type = GetWorkItemType(targetType); - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(_item.Copy(type, (Tfs.WorkItemCopyFlags)flags))); + return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy(type, (Tfs.WorkItemCopyFlags)flags))); } /// @@ -341,10 +293,7 @@ public bool IsValid() return _item.IsValid(); } - public bool IsDirty - { - get { return _item.IsDirty; } - } + public bool IsDirty => _item.IsDirty; /// /// Opens this work item for modification. @@ -384,7 +333,7 @@ public void Reset() /// public IEnumerable Validate() { - return _item.Validate().Cast().Select(field => ExceptionHandlingDynamicProxyFactory.Create(new FieldProxy(field))); + return _item.Validate().Cast().Select(field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))); } /// diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs b/src/Qwiq.Core.Soap/WorkItemLinkInfo.cs similarity index 85% rename from src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs rename to src/Qwiq.Core.Soap/WorkItemLinkInfo.cs index 9390e876..e68bb0ac 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core.Soap/WorkItemLinkInfo.cs @@ -1,8 +1,6 @@ -using System; - using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class WorkItemLinkInfo : Qwiq.WorkItemLinkInfo { diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkType.cs b/src/Qwiq.Core.Soap/WorkItemLinkType.cs similarity index 94% rename from src/Qwiq.Core.Soap/Proxies/WorkItemLinkType.cs rename to src/Qwiq.Core.Soap/WorkItemLinkType.cs index a9088625..342ebeaf 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkType.cs +++ b/src/Qwiq.Core.Soap/WorkItemLinkType.cs @@ -2,7 +2,7 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class WorkItemLinkType : Qwiq.WorkItemLinkType { diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs similarity index 94% rename from src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs rename to src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs index e9ce8451..22a1b227 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs @@ -2,7 +2,7 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd { diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs similarity index 93% rename from src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs rename to src/Qwiq.Core.Soap/WorkItemStore.cs index 00728e39..6b64c9cb 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemStoreProxy.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -8,13 +8,13 @@ using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { /// /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference /// all the TFS libraries. /// - internal class WorkItemStoreProxy : IWorkItemStore + internal class WorkItemStore : IWorkItemStore { private readonly Lazy _linkTypes; @@ -24,17 +24,17 @@ internal class WorkItemStoreProxy : IWorkItemStore private readonly Lazy _workItemStore; - internal WorkItemStoreProxy( - TfsTeamProjectCollection tfsNative, + internal WorkItemStore( + TeamFoundation.Client.TfsTeamProjectCollection tfsNative, Func queryFactory) : this( () => ExceptionHandlingDynamicProxyFactory.Create( - new TfsTeamProjectCollectionProxy(tfsNative)), + new TfsTeamProjectCollection(tfsNative)), queryFactory) { } - internal WorkItemStoreProxy( + internal WorkItemStore( Func tpcFactory, Func wisFactory, Func queryFactory) @@ -54,7 +54,7 @@ internal WorkItemStoreProxy( }); } - internal WorkItemStoreProxy( + internal WorkItemStore( Func tpcFactory, Func queryFactory) : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory) @@ -74,7 +74,7 @@ public IEnumerable Projects return _workItemStore.Value.Projects.Cast() .Select( item => ExceptionHandlingDynamicProxyFactory.Create( - new Project(item, this))); + new Project(item))); } } diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 3c1718dd..497930b7 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -3,7 +3,6 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; -using Microsoft.Qwiq.Soap.Proxies; using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Build.Client; using Microsoft.TeamFoundation.Client; @@ -32,7 +31,7 @@ public IWorkItemStore Create(AuthenticationOptions options) var tfsNative = ConnectToTfsCollection(options.Uri, credential.Credentials); var tfsProxy = ExceptionHandlingDynamicProxyFactory.Create( - new TfsTeamProjectCollectionProxy(tfsNative)); + new TfsTeamProjectCollection(tfsNative)); options.Notifications.AuthenticationSuccess( new AuthenticationSuccessNotification(credential, tfsProxy)); @@ -88,16 +87,16 @@ public static IWorkItemStoreFactory GetInstance() return Instance; } - private static TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) + private static TeamFoundation.Client.TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) { - var tfsServer = new TfsTeamProjectCollection(endpoint, credentials); + var tfsServer = new TeamFoundation.Client.TfsTeamProjectCollection(endpoint, credentials); tfsServer.EnsureAuthenticated(); return tfsServer; } private static IWorkItemStore CreateSoapWorkItemStore(IInternalTfsTeamProjectCollection tfs) { - return new WorkItemStoreProxy(() => tfs, QueryFactory.GetInstance); + return new WorkItemStore(() => tfs, QueryFactory.GetInstance); } // ReSharper disable ClassNeverInstantiated.Local diff --git a/src/Qwiq.Core.Soap/Proxies/WorkItemType.cs b/src/Qwiq.Core.Soap/WorkItemType.cs similarity index 88% rename from src/Qwiq.Core.Soap/Proxies/WorkItemType.cs rename to src/Qwiq.Core.Soap/WorkItemType.cs index baccb787..2483ffe5 100644 --- a/src/Qwiq.Core.Soap/Proxies/WorkItemType.cs +++ b/src/Qwiq.Core.Soap/WorkItemType.cs @@ -4,7 +4,7 @@ using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; -namespace Microsoft.Qwiq.Soap.Proxies +namespace Microsoft.Qwiq.Soap { internal class WorkItemType : Qwiq.WorkItemType { @@ -13,7 +13,7 @@ internal WorkItemType(Tfs.WorkItemType type) type?.Name, type?.Description, new Lazy(() => ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinitionCollection(type?.FieldDefinitions))), - () => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemProxy(type?.NewWorkItem())) + () => ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(type?.NewWorkItem())) ) { if (type == null) throw new ArgumentNullException(nameof(type)); diff --git a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs index 042e9cd4..03e7488f 100644 --- a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs @@ -1,19 +1,19 @@ -using Microsoft.Qwiq.Soap.Proxies; using Microsoft.Qwiq.Tests.Common; -using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; + + namespace Microsoft.Qwiq.Core.Tests { public abstract class IdentityDescriptorProxyTests : ContextSpecification { - protected IdentityDescriptorProxy IdentityDescriptorProxy; - protected IdentityDescriptor IdentityDescriptor; + protected Soap.IdentityDescriptor IdentityDescriptorProxy; + protected TeamFoundation.Framework.Client.IdentityDescriptor IdentityDescriptor; public override void When() { - IdentityDescriptorProxy = new IdentityDescriptorProxy(IdentityDescriptor); + IdentityDescriptorProxy = new Soap.IdentityDescriptor(IdentityDescriptor); } } @@ -25,7 +25,7 @@ public class given_an_IdentityDescriptor_when_a_proxy_is_created : IdentityDescr public override void Given() { - IdentityDescriptor = new IdentityDescriptor(IdentityType, Identifier); + IdentityDescriptor = new TeamFoundation.Framework.Client.IdentityDescriptor(IdentityType, Identifier); } [TestMethod] diff --git a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs index ff5a8cf2..0f87a2e7 100644 --- a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs +++ b/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Qwiq.Core.Tests.Mocks; -using Microsoft.Qwiq.Soap.Proxies; +using Microsoft.Qwiq.Soap; using Microsoft.Qwiq.Tests.Common; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -19,7 +19,7 @@ public abstract class IdentityManagementServiceProxyTests : ContextSpecificat public override void Given() { _identityManagementService2 = new MockIdentityManagementService2(); - Service = new IdentityManagementServiceProxy(_identityManagementService2); + Service = new IdentityManagementService(_identityManagementService2); } } diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index fc7c235c..3aeef2b8 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -4,13 +4,14 @@ using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Soap; -using Microsoft.Qwiq.Soap.Proxies; using Microsoft.Qwiq.Tests.Common; using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; +using TfsTeamProjectCollection = Microsoft.TeamFoundation.Client.TfsTeamProjectCollection; + namespace Microsoft.Qwiq.Core.Tests { [TestClass] @@ -162,7 +163,7 @@ public abstract class WorkItemStoreSoapTests : WorkItemStoreTests QueryFactory); + return new WorkItemStore((TfsTeamProjectCollection)null, s => QueryFactory); } } From 07fde3dd39cde0e92ef67599531b8348b5269d28 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 29 Mar 2017 19:13:36 -0700 Subject: [PATCH 073/251] Minimized TFS dependencies for Qwiq.Core --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 1 - src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 1 - src/Qwiq.Core/IExternalLink.cs | 2 - src/Qwiq.Core/IRevision.cs | 2 - src/Qwiq.Core/Qwiq.Core.csproj | 118 ------------------ src/Qwiq.Core/app.config | 30 +++++ src/Qwiq.Core/packages.config | 5 - test/Qwiq.Identity.Benchmark.Tests/app.config | 12 ++ test/Qwiq.Identity.Tests/app.config | 12 ++ test/Qwiq.Linq.Tests/app.config | 12 ++ test/Qwiq.Mapper.Tests/app.config | 12 ++ test/Qwiq.Relatives.Tests/app.config | 12 ++ 12 files changed, 90 insertions(+), 129 deletions(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 12cd59da..1e9b0f63 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -82,7 +82,6 @@ - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 9438bb34..291a7eac 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -168,7 +168,6 @@ - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll diff --git a/src/Qwiq.Core/IExternalLink.cs b/src/Qwiq.Core/IExternalLink.cs index da8b50a8..2fc022fc 100644 --- a/src/Qwiq.Core/IExternalLink.cs +++ b/src/Qwiq.Core/IExternalLink.cs @@ -1,5 +1,3 @@ -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - namespace Microsoft.Qwiq { public interface IExternalLink : ILink diff --git a/src/Qwiq.Core/IRevision.cs b/src/Qwiq.Core/IRevision.cs index 1eb6f5e6..133937a9 100644 --- a/src/Qwiq.Core/IRevision.cs +++ b/src/Qwiq.Core/IRevision.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; - namespace Microsoft.Qwiq { public interface IRevision diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 221f086a..1873c982 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -49,116 +49,9 @@ ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - - ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll @@ -178,7 +71,6 @@ - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll @@ -188,14 +80,6 @@ - - ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - @@ -308,10 +192,8 @@ - - \ No newline at end of file diff --git a/src/Qwiq.Core/app.config b/src/Qwiq.Core/app.config index 0a346744..348b5314 100644 --- a/src/Qwiq.Core/app.config +++ b/src/Qwiq.Core/app.config @@ -20,4 +20,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index c2cef98d..045e9f52 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -3,13 +3,8 @@ - - - - - diff --git a/test/Qwiq.Identity.Benchmark.Tests/app.config b/test/Qwiq.Identity.Benchmark.Tests/app.config index 2641b3e5..dbb474ee 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/app.config +++ b/test/Qwiq.Identity.Benchmark.Tests/app.config @@ -2,6 +2,18 @@ + + + + + + + + + + + + diff --git a/test/Qwiq.Identity.Tests/app.config b/test/Qwiq.Identity.Tests/app.config index fc5d0215..871b1c0f 100644 --- a/test/Qwiq.Identity.Tests/app.config +++ b/test/Qwiq.Identity.Tests/app.config @@ -2,6 +2,18 @@ + + + + + + + + + + + + diff --git a/test/Qwiq.Linq.Tests/app.config b/test/Qwiq.Linq.Tests/app.config index fc5d0215..871b1c0f 100644 --- a/test/Qwiq.Linq.Tests/app.config +++ b/test/Qwiq.Linq.Tests/app.config @@ -2,6 +2,18 @@ + + + + + + + + + + + + diff --git a/test/Qwiq.Mapper.Tests/app.config b/test/Qwiq.Mapper.Tests/app.config index fc5d0215..871b1c0f 100644 --- a/test/Qwiq.Mapper.Tests/app.config +++ b/test/Qwiq.Mapper.Tests/app.config @@ -2,6 +2,18 @@ + + + + + + + + + + + + diff --git a/test/Qwiq.Relatives.Tests/app.config b/test/Qwiq.Relatives.Tests/app.config index fc5d0215..871b1c0f 100644 --- a/test/Qwiq.Relatives.Tests/app.config +++ b/test/Qwiq.Relatives.Tests/app.config @@ -2,6 +2,18 @@ + + + + + + + + + + + + From 94d9b00aba1b0a1c01d9c778e049e6a95820ece3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 30 Mar 2017 14:16:10 -0700 Subject: [PATCH 074/251] Update ExceptionHandlingDynamicProxy exception handling When an exception is encountered, map and re-throw causes a loss of stack trace. Using method from .NET 4.5 to throw the mapped exception while perserving the stack trace. --- src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs index a3c2c820..a61c9519 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Runtime.ExceptionServices; using Castle.DynamicProxy; @@ -23,7 +24,8 @@ public void Intercept(IInvocation invocation) } catch (Exception e) { - throw _exceptionMapper.Map(e); + // .NET 4.5 feature: Capture an exception and re-throw it without changing the stack trace + ExceptionDispatchInfo.Capture(_exceptionMapper.Map(e)).Throw(); } } } From 0c054a713f4e06a701a10d0b9dcd17e7c0cfa013 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 30 Mar 2017 19:19:26 -0700 Subject: [PATCH 075/251] Split IWorkItem interface down Interface and implementation classes are split into smaller pieces. A base class `WorkItem` and interface `IWorkItem` exist for legacy reasons. --- Qwiq.sln.DotSettings | 2 + .../FieldDefinitionCollection.cs | 9 +- src/Qwiq.Core.Rest/Query.cs | 4 +- src/Qwiq.Core.Rest/WorkItem.cs | 181 +-------- src/Qwiq.Core.Rest/WorkItemStore.cs | 5 +- src/Qwiq.Core.Soap/QueryFactory.cs | 1 - src/Qwiq.Core.Soap/WorkItem.cs | 360 +++++++++--------- src/Qwiq.Core/CoreFieldRefNames.cs | 2 +- src/Qwiq.Core/Credentials/TfsCredentials.cs | 72 +++- src/Qwiq.Core/IIdentifiable.cs | 11 + src/{Qwiq.Mapper => Qwiq.Core}/ITypeParser.cs | 2 +- src/Qwiq.Core/IWorkItem.cs | 178 +++------ src/Qwiq.Core/IWorkItemCommon.cs | 99 +++++ src/Qwiq.Core/IWorkItemCore.cs | 24 ++ src/Qwiq.Core/IWorkItemReference.cs | 7 + src/Qwiq.Core/Qwiq.Core.csproj | 10 + src/{Qwiq.Mapper => Qwiq.Core}/TypeParser.cs | 84 ++-- src/Qwiq.Core/WorkItem.cs | 124 ++++++ src/Qwiq.Core/WorkItemCommon.cs | 89 +++++ src/Qwiq.Core/WorkItemCore.cs | 48 +++ src/Qwiq.Core/WorkItemLinkType.cs | 7 +- src/Qwiq.Mapper/IIdentifiable.cs | 6 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 2 - .../IntegrationContextSpecification.cs | 35 +- test/Qwiq.Core.Tests/LinkIntegrationTests.cs | 46 +++ test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 2 + test/Qwiq.Core.Tests/SingleIdTests.cs | 36 +- .../TypeParserTests.cs | 20 +- test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs | 2 +- ...ItemStoreComparisonContextSpecification.cs | 68 +++- test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 5 +- .../Qwiq.Mapper.Tests.csproj | 1 - test/Qwiq.Mocks/MockWorkItem.cs | 258 ++++--------- test/Qwiq.Tests.Common/ShouldExtensions.cs | 4 +- 34 files changed, 1045 insertions(+), 759 deletions(-) create mode 100644 Qwiq.sln.DotSettings create mode 100644 src/Qwiq.Core/IIdentifiable.cs rename src/{Qwiq.Mapper => Qwiq.Core}/ITypeParser.cs (89%) create mode 100644 src/Qwiq.Core/IWorkItemCommon.cs create mode 100644 src/Qwiq.Core/IWorkItemCore.cs create mode 100644 src/Qwiq.Core/IWorkItemReference.cs rename src/{Qwiq.Mapper => Qwiq.Core}/TypeParser.cs (57%) create mode 100644 src/Qwiq.Core/WorkItem.cs create mode 100644 src/Qwiq.Core/WorkItemCommon.cs create mode 100644 src/Qwiq.Core/WorkItemCore.cs create mode 100644 test/Qwiq.Core.Tests/LinkIntegrationTests.cs rename test/{Qwiq.Mapper.Tests => Qwiq.Core.Tests}/TypeParserTests.cs (95%) diff --git a/Qwiq.sln.DotSettings b/Qwiq.sln.DotSettings new file mode 100644 index 00000000..6e083e09 --- /dev/null +++ b/Qwiq.sln.DotSettings @@ -0,0 +1,2 @@ + + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs b/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs index aab71ddc..5c06a172 100644 --- a/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; @@ -7,6 +8,12 @@ namespace Microsoft.Qwiq.Rest { internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { + public FieldDefinitionCollection(IWorkItemStore store) + : base(store?.Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s)) + { + if (store == null) throw new ArgumentNullException(nameof(store)); + } + internal FieldDefinitionCollection(IEnumerable typeFields) : this(typeFields.Where(p => p != null).Select(s => s.Field)) { diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 86e52727..2be7c00b 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -58,7 +58,7 @@ internal Query( if (!m.Success) return null; DateTime retval; - if (!DateTime.TryParse(m.Value, out retval)) + if (!DateTime.TryParse(m.Groups["date"].Value, out retval)) { throw new Exception(); } @@ -106,7 +106,7 @@ public IEnumerable RunQuery() if (_ids == null) yield break; - var expand = _fields != null ? (WorkItemExpand?)null : WorkItemExpand.Fields; + var expand = _fields != null ? (WorkItemExpand?)null : WorkItemExpand.All; var qry = _ids.Partition(_workItemStore.BatchSize); var ts = qry.Select(s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, _fields, _asOf, expand, WorkItemErrorPolicy.Omit)); diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index 3e3236d0..cfd60dac 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; namespace Microsoft.Qwiq.Rest { - internal class WorkItem : IWorkItem + public class WorkItem : Qwiq.WorkItem { private readonly TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem _item; @@ -14,186 +13,26 @@ internal WorkItem(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, L _item = item; _wit = wit; Uri = new Uri(item.Url); - - } - - public string AreaPath - { - get => GetValue(CoreFieldRefNames.AreaPath); - set => SetValue(CoreFieldRefNames.AreaPath, value); - } - - public string AssignedTo - { - get => GetValue(CoreFieldRefNames.AssignedTo); - set => SetValue(CoreFieldRefNames.AssignedTo, value); + Url = item.Url; } - public int AttachedFileCount => GetValue(CoreFieldRefNames.AttachedFileCount); - - public IEnumerable Attachments => throw new NotImplementedException(); - - public string ChangedBy - { - get => GetValue(CoreFieldRefNames.ChangedBy); - set => SetValue(CoreFieldRefNames.ChangedBy, value); - } + public override int Id => _item.Id.GetValueOrDefault(0); - public DateTime ChangedDate - { - get => GetValue(CoreFieldRefNames.ChangedDate); - set => SetValue(CoreFieldRefNames.ChangedDate, value); - } - - public string CreatedBy - { - get => GetValue(CoreFieldRefNames.CreatedBy); - set => SetValue(CoreFieldRefNames.CreatedBy, value); - } - - public DateTime CreatedDate - { - get => GetValue(CoreFieldRefNames.CreatedDate); - set => SetValue(CoreFieldRefNames.CreatedDate, value); - } - - public string Description - { - get => GetValue(CoreFieldRefNames.Description); - set => SetValue(CoreFieldRefNames.Description, value); - } - - public int ExternalLinkCount => GetValue(CoreFieldRefNames.ExternalLinkCount); - - public IFieldCollection Fields => throw new NotImplementedException(); - - public string History - { - get => GetValue(CoreFieldRefNames.History) as string ?? string.Empty; - set => SetValue(CoreFieldRefNames.History, value); - } - - public int HyperLinkCount => GetValue(CoreFieldRefNames.HyperLinkCount); - - public int Id => _item.Id.GetValueOrDefault(0); - - public bool IsDirty => throw new NotImplementedException(); - - public string IterationPath - { - get => GetValue(CoreFieldRefNames.IterationPath); - set => SetValue(CoreFieldRefNames.IterationPath, value); - } - - public string Keywords + public override string Keywords { get => GetValue(WorkItemFields.Keywords); set => SetValue(WorkItemFields.Keywords, value); } - public ICollection Links => throw new NotImplementedException(); + public override int Rev => _item.Rev.GetValueOrDefault(0); - public int RelatedLinkCount => GetValue(CoreFieldRefNames.RelatedLinkCount); + public override IWorkItemType Type => _wit.Value; - public long Rev => GetValue(CoreFieldRefNames.Rev); + public override Uri Uri { get; } - public DateTime RevisedDate => GetValue(CoreFieldRefNames.RevisedDate); - - public long Revision => Rev; - - public IEnumerable Revisions => throw new NotImplementedException(); - - public string State - { - get => GetValue(CoreFieldRefNames.State); - set => SetValue(CoreFieldRefNames.State, value); - } - - public string Tags - { - get => GetValue(CoreFieldRefNames.Tags); - set => SetValue(CoreFieldRefNames.Tags, value); - } - - public string Title - { - get => GetValue(CoreFieldRefNames.Title); - set => SetValue(CoreFieldRefNames.Title, value); - } - - public IWorkItemType Type => _wit.Value; - - public Uri Uri { get; } - - public object this[string name] - { - get => GetValue(name); - set => SetValue(name, value); - } - - public void ApplyRules(bool doNotUpdateChangedBy = false) - { - throw new NotImplementedException(); - } - - public void Close() - { - } - - public IWorkItem Copy() - { - throw new NotImplementedException(); - } - - public IHyperlink CreateHyperlink(string location) - { - throw new NotImplementedException(); - } - - public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) - { - throw new NotImplementedException(); - } - - public bool IsValid() - { - throw new NotImplementedException(); - } - - public void Open() - { - } - - public void PartialOpen() - { - } - - public void Reset() - { - throw new NotImplementedException(); - } - - public void Save() - { - throw new NotImplementedException(); - } - - public void Save(SaveFlags saveFlags) - { - throw new NotImplementedException(); - } - - public IEnumerable Validate() - { - throw new NotImplementedException(); - } - - private T GetValue(string field) - { - return (T)GetValue(field); - } + public override string Url { get; } - private object GetValue(string field) + protected override object GetValue(string field) { //if (!Type.FieldDefinitions.Contains(field)) //{ @@ -205,7 +44,7 @@ private object GetValue(string field) return !_item.Fields.TryGetValue(field, out object val) ? null : val; } - private void SetValue(string field, object value) + protected override void SetValue(string field, object value) { _item.Fields[field] = value; } diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 974dd3a9..2aad2471 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -28,7 +28,7 @@ internal class WorkItemStore : IWorkItemStore private readonly Lazy> _projects; - + private Lazy _fieldDefinitions; internal WorkItemStore( Func tpcFactory, @@ -75,11 +75,12 @@ WorkItemLinkTypeCollection ValueFactory() .ToList(); } }); + _fieldDefinitions = new Lazy(() => new FieldDefinitionCollection(this)); } public TfsCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; - public IFieldDefinitionCollection FieldDefinitions => throw new NotImplementedException(); + public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions.Value; public IEnumerable Projects => _projects.Value; diff --git a/src/Qwiq.Core.Soap/QueryFactory.cs b/src/Qwiq.Core.Soap/QueryFactory.cs index acfbc292..3736c89b 100644 --- a/src/Qwiq.Core.Soap/QueryFactory.cs +++ b/src/Qwiq.Core.Soap/QueryFactory.cs @@ -3,7 +3,6 @@ using System.Linq; using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { diff --git a/src/Qwiq.Core.Soap/WorkItem.cs b/src/Qwiq.Core.Soap/WorkItem.cs index d2d1eb77..5ee1a519 100644 --- a/src/Qwiq.Core.Soap/WorkItem.cs +++ b/src/Qwiq.Core.Soap/WorkItem.cs @@ -9,354 +9,295 @@ namespace Microsoft.Qwiq.Soap { /// - /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference - /// all the TFS libraries. + /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference + /// all the TFS libraries. /// - internal class WorkItem : IWorkItem + public class WorkItem : Qwiq.WorkItem, IWorkItem { private readonly Tfs.WorkItem _item; internal WorkItem(Tfs.WorkItem item) + : base(ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(item.Type))) { _item = item; - } - - public string AssignedTo - { - get => _item[Tfs.CoreFieldReferenceNames.AssignedTo].ToString(); - set => _item[Tfs.CoreFieldReferenceNames.AssignedTo] = value; + Url = item.Uri.ToString(); } /// - /// Gets or sets the string value of the AreaPath field for this work item. + /// Gets or sets the string value of the AreaPath field for this work item. /// - public string AreaPath + public override string AreaPath { get => _item.AreaPath; set => _item.AreaPath = value; } /// - /// Gets the number of attached files for this work item. + /// Gets the number of attached files for this work item. /// - public int AttachedFileCount => _item.AttachedFileCount; + public override int AttachedFileCount => _item.AttachedFileCount; /// - /// Gets the Microsoft.TeamFoundation.WorkItemTracking.Client.AttachmentCollection - /// object that represents the attachments that belong to this work item. + /// Gets the Microsoft.TeamFoundation.WorkItemTracking.Client.AttachmentCollection + /// object that represents the attachments that belong to this work item. /// - public IEnumerable Attachments + public override IEnumerable Attachments { - get { return _item.Attachments.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Attachment(item))); } + get + { + return _item.Attachments.Cast() + .Select( + item => ExceptionHandlingDynamicProxyFactory + .Create(new Attachment(item))); + } } - /// - /// Gets the string value of the ChangedBy field for this work item. - /// - public string ChangedBy => _item.ChangedBy; + /// - /// Gets the System.DateTime object that represents the date and time that this - /// work item was last changed. + /// Gets the string value of the ChangedBy field for this work item. /// - public DateTime ChangedDate => _item.ChangedDate; + public override string ChangedBy => _item.ChangedBy; /// - /// Gets the string value of the CreatedBy field for this work item. + /// Gets the System.DateTime object that represents the date and time that this + /// work item was last changed. /// - public string CreatedBy => _item.CreatedBy; + public override DateTime ChangedDate => _item.ChangedDate; /// - /// Gets the System.DateTime object that represents the date and time that this - /// work item was created. + /// Gets the string value of the CreatedBy field for this work item. /// - public DateTime CreatedDate => _item.CreatedDate; + public override string CreatedBy => _item.CreatedBy; /// - /// Closes this WorkItem instance and frees memory that is associated with it. + /// Gets the System.DateTime object that represents the date and time that this + /// work item was created. /// - public void Close() - { - _item.Close(); - } + public override DateTime CreatedDate => _item.CreatedDate; /// - /// Gets or sets a string that describes this work item. + /// Gets or sets a string that describes this work item. /// - public string Description + public override string Description { get => _item.Description; set => _item.Description = value; } /// - /// Gets the number of external links in this work item. + /// Gets the number of external links in this work item. /// - public int ExternalLinkCount => _item.ExternalLinkCount; + public override int ExternalLinkCount => _item.ExternalLinkCount; - public IFieldCollection Fields => ExceptionHandlingDynamicProxyFactory.Create(new FieldCollection(_item.Fields)); + public override IFieldCollection Fields => ExceptionHandlingDynamicProxyFactory.Create( + new FieldCollection(_item.Fields)); /// - /// Gets or sets the string value of the History field for this work item. + /// Gets or sets the string value of the History field for this work item. /// - public string History + public override string History { get => _item.History; set => _item.History = value; } /// - /// Gets the number of hyperlinks in this work item. + /// Gets the number of hyperlinks in this work item. /// - public int HyperLinkCount => _item.HyperLinkCount; + public new int HyperLinkCount => _item.HyperLinkCount; /// - /// Gets the ID of this work item. + /// Gets the ID of this work item. /// - public int Id => _item.Id; + public override int Id => _item.Id; + + public override bool IsDirty => _item.IsDirty; /// - /// Gets or sets the string value of the IterationPath field of this work item. + /// Gets or sets the string value of the IterationPath field of this work item. /// - public string IterationPath + public override string IterationPath { get => _item.IterationPath; set => _item.IterationPath = value; } + public override string Keywords + { + get => (string)_item[WorkItemFields.Keywords]; + set => _item[WorkItemFields.Keywords] = value; + } + /// - /// Gets the collection of the links in this work item. + /// Gets the collection of the links in this work item. /// /// - /// Gets the links of the work item in this revision. + /// Gets the links of the work item in this revision. /// - public ICollection Links => ExceptionHandlingDynamicProxyFactory.Create>(new LinkCollection(_item)); + public override ICollection Links => ExceptionHandlingDynamicProxyFactory.Create>( + new LinkCollection(_item)); /// - /// Gets the number of related links of this work item. + /// Gets the number of related links of this work item. /// - public int RelatedLinkCount => _item.RelatedLinkCount; + public override int RelatedLinkCount => _item.RelatedLinkCount; + + public override int Rev => _item.Rev; /// - /// Gets a System.DateTime object that represents the revision date and time - /// of this work item. + /// Gets a System.DateTime object that represents the revision date and time + /// of this work item. /// - public DateTime RevisedDate => _item.RevisedDate; + public override DateTime RevisedDate => _item.RevisedDate; /// - /// Gets the integer that represents the revision number of this work item. + /// Gets the integer that represents the revision number of this work item. /// - public long Revision => _item.Revision; + public override int Revision => _item.Revision; /// - /// Gets an object that represents a collection of valid revision numbers for this work - /// item. + /// Gets an object that represents a collection of valid revision numbers for this work + /// item. /// - public IEnumerable Revisions + public override IEnumerable Revisions { - get { return _item.Revisions.Cast().Select(r => ExceptionHandlingDynamicProxyFactory.Create(new Revision(r))); } + get + { + return _item.Revisions.Cast() + .Select(r => ExceptionHandlingDynamicProxyFactory.Create(new Revision(r))); + } } /// - /// Gets or sets a string that describes the state of this work item. + /// Gets or sets a string that describes the state of this work item. /// - public string State + public override string State { get => _item.State; set => _item.State = value; } - public string Tags - { - get => _item.Tags; - set => _item["Tags"] = value; - } + - public string Keywords + public override string Tags { - get => (string)_item[WorkItemFields.Keywords]; - set => _item[WorkItemFields.Keywords] = value; + get => _item.Tags; + set => _item.Tags = value; } /// - /// Gets or sets a string that describes the title of this work item. + /// Gets or sets a string that describes the title of this work item. /// - public string Title + public override string Title { get => _item.Title; set => _item.Title = value; } /// - /// Gets a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType object - /// that represents the type of this work item. - /// - /// - /// The Type property is null. - /// - public IWorkItemType Type => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(_item.Type)); - - /// - /// Gets the uniform resource identifier (System.Uri) of this work item. + /// Gets the uniform resource identifier (System.Uri) of this work item. /// - public Uri Uri => _item.Uri; + public override Uri Uri => _item.Uri; - public long Rev => _item.Rev; + public override string Url { get; } - public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) + public override void ApplyRules(bool doNotUpdateChangedBy = false) { - var rawLinkTypeEnd = LinkTypeEndMapper.Map(_item.Store, linkTypeEnd); - return ExceptionHandlingDynamicProxyFactory.Create(new RelatedLink(new Tfs.RelatedLink(rawLinkTypeEnd, relatedWorkItem.Id))); - } - - public IHyperlink CreateHyperlink(string location) - { - return ExceptionHandlingDynamicProxyFactory.Create(new Hyperlink(new Tfs.Hyperlink(location))); + _item.ApplyRules(doNotUpdateChangedBy); } /// - /// Gets or sets the value of a field in this work item that is specified by - /// the field name. + /// Closes this WorkItem instance and frees memory that is associated with it. /// - /// - /// The string that is passed in name could be either the field name or a reference name. - /// - /// The object that is contained in this field. - /// - /// The name parameter is null. - /// - public object this[string name] + public override void Close() { - get => _item[name]; - set => _item[name] = value; + _item.Close(); } /// - /// Creates a copy of this WorkItem instance. + /// Creates a copy of this WorkItem instance. /// /// A new WorkItem instance that is a copy of this WorkItem instance. - public IWorkItem Copy() + public override IWorkItem Copy() { return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy())); } - /// - /// Creates a copy of this WorkItem instance that is of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType. - /// - /// The type of the target work item. - /// - /// A new WorkItem instance of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType - /// that is a copy of this WorkItem instance. - /// - /// - /// Thrown when targetType is null. - /// - public IWorkItem Copy(IWorkItemType targetType) - { - var type = GetWorkItemType(targetType); - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy(type))); - } - - private Tfs.WorkItemType GetWorkItemType(IWorkItemType type) + public override IHyperlink CreateHyperlink(string location) { - var workItemTypes = _item.Project.WorkItemTypes.Cast(); - return workItemTypes.Single(item => item.Name == type.Name); + return ExceptionHandlingDynamicProxyFactory.Create(new Hyperlink(new Tfs.Hyperlink(location))); } - /// - /// Creates a copy of this WorkItem instance that is of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType. - /// - /// The type of the target work item. - /// Flags that specify items to copy in addition to fields. - /// - /// A new WorkItem instance of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType - /// that is a copy of this WorkItem instance. - /// - /// - /// Thrown when targetType is null. - /// - public IWorkItem Copy(IWorkItemType targetType, WorkItemCopyFlags flags) + public override IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) { - var type = GetWorkItemType(targetType); - - return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy(type, (Tfs.WorkItemCopyFlags)flags))); + var rawLinkTypeEnd = LinkTypeEndMapper.Map(_item.Store, linkTypeEnd); + return ExceptionHandlingDynamicProxyFactory.Create( + new RelatedLink(new Tfs.RelatedLink(rawLinkTypeEnd, relatedWorkItem.Id))); } /// - /// Validates the fields of this work item. + /// Validates the fields of this work item. /// /// - /// True if all fields are valid. False if at least one field is not valid. + /// True if all fields are valid. False if at least one field is not valid. /// - public bool IsValid() + public override bool IsValid() { return _item.IsValid(); } - public bool IsDirty => _item.IsDirty; - /// - /// Opens this work item for modification. + /// Opens this work item for modification. /// - public void Open() + public override void Open() { _item.Open(); } /// - /// Opens this work item for modification when transmitting minimal amounts of data over the network. + /// Opens this work item for modification when transmitting minimal amounts of data over the network. /// /// - /// This WorkItem instance does not belong to a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemCollection. + /// This WorkItem instance does not belong to a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemCollection. /// /// - /// This WorkItem instance could not be opened for edit correctly. + /// This WorkItem instance could not be opened for edit correctly. /// - public void PartialOpen() + public override void PartialOpen() { _item.PartialOpen(); } /// - /// Reverts all changes that were made since the last save. + /// Reverts all changes that were made since the last save. /// - public void Reset() + public override void Reset() { _item.Reset(); } /// - /// Gets an ArrayList of fields in this work item that are not valid. - /// - /// - /// An ArrayList of the fields in this work item that are not valid. - /// - public IEnumerable Validate() - { - return _item.Validate().Cast().Select(field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))); - } - - /// - /// Saves any pending changes on this work item. + /// Saves any pending changes on this work item. /// - public void Save() + public override void Save() { _item.Save(); } /// - /// Saves any pending changes on this work item. + /// Saves any pending changes on this work item. /// /// - /// If set to Microsoft.TeamFoundation.WorkItemTracking.Client.SaveFlags.MergeLinks, - /// does not return errors if the link that is being added already exists or - /// the link that is being removed was already removed. + /// If set to Microsoft.TeamFoundation.WorkItemTracking.Client.SaveFlags.MergeLinks, + /// does not return errors if the link that is being added already exists or + /// the link that is being removed was already removed. /// - public void Save(SaveFlags saveFlags) + public override void Save(SaveFlags saveFlags) { try { - _item.Save((Tfs.SaveFlags) saveFlags); + _item.Save((Tfs.SaveFlags)saveFlags); } catch (Tfs.ItemAlreadyUpdatedOnServerException ex) { @@ -368,10 +309,71 @@ public void Save(SaveFlags saveFlags) } } - public void ApplyRules(bool doNotUpdateChangedBy = false) + /// + /// Gets an ArrayList of fields in this work item that are not valid. + /// + /// + /// An ArrayList of the fields in this work item that are not valid. + /// + public override IEnumerable Validate() { - _item.ApplyRules(doNotUpdateChangedBy); + return _item.Validate() + .Cast() + .Select(field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))); } - } -} + /// + /// Creates a copy of this WorkItem instance that is of the specified + /// Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType. + /// + /// The type of the target work item. + /// + /// A new WorkItem instance of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType + /// that is a copy of this WorkItem instance. + /// + /// + /// Thrown when targetType is null. + /// + public IWorkItem Copy(IWorkItemType targetType) + { + var type = GetWorkItemType(targetType); + return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_item.Copy(type))); + } + + /// + /// Creates a copy of this WorkItem instance that is of the specified + /// Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType. + /// + /// The type of the target work item. + /// Flags that specify items to copy in addition to fields. + /// + /// A new WorkItem instance of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType + /// that is a copy of this WorkItem instance. + /// + /// + /// Thrown when targetType is null. + /// + public IWorkItem Copy(IWorkItemType targetType, WorkItemCopyFlags flags) + { + var type = GetWorkItemType(targetType); + + return ExceptionHandlingDynamicProxyFactory.Create( + new WorkItem(_item.Copy(type, (Tfs.WorkItemCopyFlags)flags))); + } + + protected override object GetValue(string name) + { + return _item[name]; + } + + protected override void SetValue(string name, object value) + { + _item[name] = value; + } + + private Tfs.WorkItemType GetWorkItemType(IWorkItemType type) + { + return _item.Project.WorkItemTypes[type.Name]; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/CoreFieldRefNames.cs b/src/Qwiq.Core/CoreFieldRefNames.cs index faecaa89..fc70b9e6 100644 --- a/src/Qwiq.Core/CoreFieldRefNames.cs +++ b/src/Qwiq.Core/CoreFieldRefNames.cs @@ -72,7 +72,7 @@ public static class CoreFieldRefNames public const string WorkItemType = "System.WorkItemType"; - public static IEnumerable All => NameLookup.Keys; + public static IEnumerable All => NameLookup.Keys.Except(new[]{LinkType}); public static IReadOnlyDictionary CoreFieldIdLookup { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase) diff --git a/src/Qwiq.Core/Credentials/TfsCredentials.cs b/src/Qwiq.Core/Credentials/TfsCredentials.cs index aa32ef92..f9faa47a 100644 --- a/src/Qwiq.Core/Credentials/TfsCredentials.cs +++ b/src/Qwiq.Core/Credentials/TfsCredentials.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; using Microsoft.VisualStudio.Services.Common; @@ -5,15 +6,80 @@ namespace Microsoft.Qwiq.Credentials { [DebuggerStepThrough] - public sealed class TfsCredentials + public sealed class TfsCredentials : IEquatable, IEquatable { public TfsCredentials(VssCredentials credentials) { - Credentials = credentials; + Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); } + public bool Equals(TfsCredentials other) + { + return VssCredentialsComparer.Instance.Equals(Credentials, other?.Credentials); + } + + public bool Equals(VssCredentials other) + { + return VssCredentialsComparer.Instance.Equals(Credentials, other); + } + public override bool Equals(object obj) + { + return VssCredentialsComparer.Instance.Equals(Credentials, (obj as TfsCredentials)?.Credentials); + } - internal VssCredentials Credentials { get; private set; } + public override int GetHashCode() + { + return VssCredentialsComparer.Instance.GetHashCode(Credentials); + } + + internal VssCredentials Credentials { get; } + } + + public class VssCredentialsComparer : GenericComparer + { + public static VssCredentialsComparer Instance => Nested.Instance; + + private VssCredentialsComparer() + { + } + + public override bool Equals(VssCredentials x, VssCredentials y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + + return GenericComparer.Default.Equals(x.Windows, y.Windows) + && GenericComparer.Default.Equals(x.Federated, y.Federated); + } + + public override int GetHashCode(VssCredentials obj) + { + unchecked + { + var hash = 27; + hash = (13 * hash) ^ (obj.Windows != null ? obj.Windows.GetHashCode() : 0); + hash = (13 * hash) ^ (obj.Federated != null ? obj.Federated.GetHashCode() : 0); + + return hash; + } + } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly VssCredentialsComparer Instance = new VssCredentialsComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IIdentifiable.cs b/src/Qwiq.Core/IIdentifiable.cs new file mode 100644 index 00000000..d2f38e80 --- /dev/null +++ b/src/Qwiq.Core/IIdentifiable.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Qwiq +{ + /// + /// This allows an object to be identified, K is the identifier (AKA Key) + /// + /// + public interface IIdentifiable + { + TKey Id { get; } + } +} \ No newline at end of file diff --git a/src/Qwiq.Mapper/ITypeParser.cs b/src/Qwiq.Core/ITypeParser.cs similarity index 89% rename from src/Qwiq.Mapper/ITypeParser.cs rename to src/Qwiq.Core/ITypeParser.cs index 77a346e9..c6063489 100644 --- a/src/Qwiq.Mapper/ITypeParser.cs +++ b/src/Qwiq.Core/ITypeParser.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Qwiq.Mapper +namespace Microsoft.Qwiq { public interface ITypeParser { diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 2337d837..80812ac3 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -4,225 +4,143 @@ namespace Microsoft.Qwiq { /// - /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference - /// all the TFS libraries. + /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference + /// all the TFS libraries. /// - public interface IWorkItem + public interface IWorkItem : IWorkItemCommon { - /// - /// Gets or sets the string value of the AreaPath field for this work item. - /// - string AreaPath { get; set; } - - string AssignedTo { get; set; } - - /// - /// Gets the number of attached files for this work item. - /// - int AttachedFileCount { get; } + new int AttachedFileCount { get; } IEnumerable Attachments { get; } - /// - /// Gets the string value of the ChangedBy field for this work item. - /// - string ChangedBy { get; } - - /// - /// Gets the System.DateTime object that represents the date and time that this - /// work item was last changed. - /// - DateTime ChangedDate { get; } - - /// - /// Gets the string value of the CreatedBy field for this work item. - /// - string CreatedBy { get; } - - /// - /// Gets the System.DateTime object that represents the date and time that this - /// work item was created. - /// - DateTime CreatedDate { get; } + new DateTime ChangedDate { get; } - /// - /// Gets or sets a string that describes this work item. - /// - string Description { get; set; } + new DateTime CreatedDate { get; } - /// - /// Gets the number of external links in this work item. - /// - int ExternalLinkCount { get; } + new int ExternalLinkCount { get; } IFieldCollection Fields { get; } - /// - /// Gets or sets the string value of the History field for this work item. - /// - string History { get; set; } + new int HyperLinkCount { get; } - /// - /// Gets the number of hyperlinks in this work item. - /// - int HyperLinkCount { get; } + new int RelatedLinkCount { get; } + + new DateTime RevisedDate { get; } /// - /// Gets the ID of this work item. + /// Gets the ID of this work item. /// - int Id { get; } + new int Id { get; } bool IsDirty { get; } - /// - /// Gets or sets the string value of the IterationPath field of this work item. - /// - string IterationPath { get; set; } - string Keywords { get; set; } /// - /// Gets the links of the work item in this revision. + /// Gets the links of the work item in this revision. /// ICollection Links { get; } - /// - /// Gets the number of related links of this work item. - /// - int RelatedLinkCount { get; } - - long Rev { get; } - - /// - /// Gets a System.DateTime object that represents the revision date and time - /// of this work item. - /// - DateTime RevisedDate { get; } + new int Rev { get; } /// - /// Gets the integer that represents the revision number of this work item. + /// Gets the integer that represents the revision number of this work item. /// - long Revision { get; } - + /// int Revision { get; } /// - /// Gets an object that represents a collection of valid revision numbers for this work - /// item. + /// Gets an object that represents a collection of valid revision numbers for this work + /// item. /// IEnumerable Revisions { get; } /// - /// Gets or sets a string that describes the state of this work item. - /// - string State { get; set; } - - /// - /// Gets or sets a string of all the tags on this work item. - /// - string Tags { get; set; } - - /// - /// Gets or sets a string that describes the title of this work item. - /// - string Title { get; set; } - - /// - /// Gets a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType object - /// that represents the type of this work item. + /// Gets a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType object + /// that represents the type of this work item. /// /// - /// The Type property is null. + /// The Type property is null. /// IWorkItemType Type { get; } /// - /// Gets the uniform resource identifier (System.Uri) of this work item. + /// Gets the uniform resource identifier (System.Uri) of this work item. /// + [Obsolete( + "This property is deprecated and will be removed in a future release. See IWorkItemReference.Url instead.")] Uri Uri { get; } /// - /// Gets or sets the value of a field in this work item that is specified by - /// the field name. + /// Applies the server rules for validation and fix up to the work item. /// - /// - /// The string that is passed in name could be either the field name or a reference name. + /// + /// If true, will set ChangedBy to the user context of the . + /// If false, ChangedBy will not be modified. /// - /// The object that is contained in this field. - /// - /// The name parameter is null. - /// - object this[string name] { get; set; } + /// + /// Use ApplyRules(true) in the case where you want "transparent fix ups". + /// + void ApplyRules(bool doNotUpdateChangedBy = false); /// - /// Closes this WorkItem instance and frees memory that is associated with it. + /// Closes this WorkItem instance and frees memory that is associated with it. /// void Close(); /// - /// Creates a copy of this WorkItem instance. + /// Creates a copy of this WorkItem instance. /// /// A new WorkItem instance that is a copy of this WorkItem instance. IWorkItem Copy(); + IHyperlink CreateHyperlink(string location); IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem); /// - /// Validates the fields of this work item. + /// Validates the fields of this work item. /// /// - /// True if all fields are valid. False if at least one field is not valid. + /// True if all fields are valid. False if at least one field is not valid. /// bool IsValid(); /// - /// Opens this work item for modification. + /// Opens this work item for modification. /// void Open(); /// - /// Opens this work item for modification when transmitting minimal amounts of data over the network. + /// Opens this work item for modification when transmitting minimal amounts of data over the network. /// /// This WorkItem instance does not belong to a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemCollection. /// This WorkItem instance could not be opened for edit correctly. void PartialOpen(); /// - /// Reverts all changes that were made since the last save. + /// Reverts all changes that were made since the last save. /// void Reset(); /// - /// Saves any pending changes on this work item. + /// Saves any pending changes on this work item. /// void Save(); /// - /// Saves any pending changes on this work item. + /// Saves any pending changes on this work item. /// /// - /// If set to , does not return errors if the link that - /// is being added already exists or the link that is being removed was already removed. + /// If set to , does not return errors if the link that + /// is being added already exists or the link that is being removed was already removed. /// void Save(SaveFlags saveFlags); /// - /// Gets an ArrayList of fields in this work item that are not valid. + /// Gets an ArrayList of fields in this work item that are not valid. /// /// - /// An ArrayList of the fields in this work item that are not valid. + /// An ArrayList of the fields in this work item that are not valid. /// IEnumerable Validate(); - - /// - /// Applies the server rules for validation and fix up to the work item. - /// - /// - /// If true, will set ChangedBy to the user context of the . - /// If false, ChangedBy will not be modified. - /// - /// - /// Use ApplyRules(true) in the case where you want "transparent fix ups". - /// - void ApplyRules(bool doNotUpdateChangedBy = false); } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemCommon.cs b/src/Qwiq.Core/IWorkItemCommon.cs new file mode 100644 index 00000000..58520e11 --- /dev/null +++ b/src/Qwiq.Core/IWorkItemCommon.cs @@ -0,0 +1,99 @@ +using System; + +namespace Microsoft.Qwiq +{ + public interface IWorkItemCommon : IWorkItemCore + { + int? AreaId { get; set; } + + /// + /// Gets or sets the string value of the AreaPath field for this work item. + /// + string AreaPath { get; set; } + + string AssignedTo { get; set; } + + /// + /// Gets the number of attached files for this work item. + /// + int? AttachedFileCount { get; } + + /// + /// Gets the string value of the ChangedBy field for this work item. + /// + string ChangedBy { get; } + + /// + /// Gets the System.DateTime object that represents the date and time that this + /// work item was last changed. + /// + DateTime? ChangedDate { get; } + + /// + /// Gets the string value of the CreatedBy field for this work item. + /// + string CreatedBy { get; } + + /// + /// Gets the System.DateTime object that represents the date and time that this + /// work item was created. + /// + DateTime? CreatedDate { get; } + + /// + /// Gets or sets a string that describes this work item. + /// + string Description { get; set; } + + /// + /// Gets the number of external links in this work item. + /// + int? ExternalLinkCount { get; } + + string History { get; set; } + + /// + /// Gets the number of hyperlinks in this work item. + /// + int? HyperLinkCount { get; } + + int? IterationId { get; set; } + + /// + /// Gets or sets the string value of the IterationPath field of this work item. + /// + string IterationPath { get; set; } + + /// + /// Gets the number of related links of this work item. + /// + int? RelatedLinkCount { get; } + + /// + /// Gets a System.DateTime object that represents the revision date and time + /// of this work item. + /// + DateTime? RevisedDate { get; } + + /// + /// Gets or sets a string that describes the state of this work item. + /// + string State { get; set; } + + /// + /// Gets or sets a string of all the tags on this work item. + /// + string Tags { get; set; } + + /// + /// Gets or sets a string that describes the title of this work item. + /// + string Title { get; set; } + + int? Watermark { get; } + + string WorkItemType { get; } + + // Team Project? + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemCore.cs b/src/Qwiq.Core/IWorkItemCore.cs new file mode 100644 index 00000000..4b5b8f40 --- /dev/null +++ b/src/Qwiq.Core/IWorkItemCore.cs @@ -0,0 +1,24 @@ +namespace Microsoft.Qwiq +{ + public interface IWorkItemCore : IWorkItemReference + { + int? Rev { get; } + + /// + /// Gets or sets the value of a field in this work item that is specified by + /// the field name. + /// + /// + /// The string that is passed in name could be either the field name or a reference name. + /// + /// The object that is contained in this field. + /// + /// The name parameter is null. + /// + object this[string name] { get; set; } + + // Relations + + // Links + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemReference.cs b/src/Qwiq.Core/IWorkItemReference.cs new file mode 100644 index 00000000..1386f07a --- /dev/null +++ b/src/Qwiq.Core/IWorkItemReference.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Qwiq +{ + public interface IWorkItemReference : IIdentifiable + { + string Url { get; } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 1873c982..655d6415 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + CS0108 pdbonly @@ -128,6 +129,7 @@ + @@ -145,10 +147,14 @@ ITeamFoundationIdentity.cs + + + + @@ -160,8 +166,12 @@ + + + + diff --git a/src/Qwiq.Mapper/TypeParser.cs b/src/Qwiq.Core/TypeParser.cs similarity index 57% rename from src/Qwiq.Mapper/TypeParser.cs rename to src/Qwiq.Core/TypeParser.cs index 4b27bacd..dd78395a 100644 --- a/src/Qwiq.Mapper/TypeParser.cs +++ b/src/Qwiq.Core/TypeParser.cs @@ -1,13 +1,15 @@ using System; using System.ComponentModel; -namespace Microsoft.Qwiq.Mapper +namespace Microsoft.Qwiq { /// - /// Helper class that converts objects (as they are returned from the TFS API) to usable types. + /// Helper class that converts objects (as they are returned from the TFS API) to usable types. /// public class TypeParser : ITypeParser { + public static ITypeParser Default => Nested.Instance; + public object Parse(Type destinationType, object value, object defaultValue) { return ParseImpl( @@ -38,45 +40,27 @@ private static object GetDefaultValueOfType(Type type) private static bool IsGenericNullable(Type type) { - return type.IsGenericType - && type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); + return type.IsGenericType && type.GetGenericTypeDefinition() + == typeof(Nullable<>).GetGenericTypeDefinition(); } private static object ParseImpl(Type destinationType, object value, Lazy defaultValueFactory) { // If the incoming value is null, return the default value - if (ValueRepresentsNull(value)) - { - return defaultValueFactory.Value; - } + if (ValueRepresentsNull(value)) return defaultValueFactory.Value; // Quit if no type conversion is actually required - if (value.GetType() == destinationType) - { - return value; - } + if (value.GetType() == destinationType) return value; - if (destinationType.IsInstanceOfType(value)) - { - return value; - } + if (destinationType.IsInstanceOfType(value)) return value; object result; - if (TryConvert(destinationType, value, out result)) - { - return result; - } + if (TryConvert(destinationType, value, out result)) return result; - if (IsGenericNullable(destinationType) && defaultValueFactory.Value == null) - { - return null; - } + if (IsGenericNullable(destinationType) && defaultValueFactory.Value == null) return null; - if (TryConvert(destinationType, defaultValueFactory.Value, out result)) - { - return result; - } + if (TryConvert(destinationType, defaultValueFactory.Value, out result)) return result; return null; } @@ -86,7 +70,6 @@ private static bool TryConvert(Type destinationType, object value, out object re var valueType = value.GetType(); var typeConverter = TypeDescriptor.GetConverter(valueType); if (typeConverter.CanConvertTo(destinationType)) - { try { result = typeConverter.ConvertTo(value, destinationType); @@ -94,14 +77,12 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } - } typeConverter = TypeDescriptor.GetConverter(destinationType); if (typeConverter.CanConvertFrom(valueType)) - { try { result = typeConverter.ConvertFrom(value); @@ -109,10 +90,31 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } + + if (value != null) + { + var val = value.ToString(); + if (!string.IsNullOrEmpty(val)) + { + if (typeConverter.IsValid(val)) + { + try + { + result = typeConverter.ConvertFromString(val); + return true; + } + // ReSharper disable EmptyGeneralCatchClause + catch + // ReSharper restore EmptyGeneralCatchClause + { + } + } + } } + result = null; return false; } @@ -121,5 +123,21 @@ private static bool ValueRepresentsNull(object value) { return value == null || value == DBNull.Value; } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly ITypeParser Instance = new TypeParser(); + + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs new file mode 100644 index 00000000..07fc536b --- /dev/null +++ b/src/Qwiq.Core/WorkItem.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Qwiq +{ + /// + /// A compatability class + /// + /// + /// + public abstract class WorkItem : WorkItemCommon, IWorkItem + { + private readonly IWorkItemType _type; + + protected WorkItem(IWorkItemType type) + { + _type = type ?? throw new ArgumentNullException(nameof(type)); + } + + protected WorkItem() + { + } + + public virtual int Revision => Rev; + + public new virtual int AttachedFileCount => base.AttachedFileCount.GetValueOrDefault(0); + + public virtual IEnumerable Attachments => throw new NotImplementedException(); + + public new virtual DateTime ChangedDate => base.ChangedDate.GetValueOrDefault(DateTime.MinValue); + + public new virtual DateTime CreatedDate => base.CreatedDate.GetValueOrDefault(DateTime.MinValue); + + public new virtual int ExternalLinkCount => base.ExternalLinkCount.GetValueOrDefault(0); + + public virtual IFieldCollection Fields => throw new NotImplementedException(); + + public new virtual int HyperLinkCount => base.HyperLinkCount.GetValueOrDefault(0); + + public new virtual int Id => base.Id.GetValueOrDefault(0); + + public virtual bool IsDirty => throw new NotImplementedException(); + + public virtual string Keywords + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public virtual ICollection Links => throw new NotImplementedException(); + + public new virtual int RelatedLinkCount => base.RelatedLinkCount.GetValueOrDefault(0); + + public new virtual int Rev => base.Rev.GetValueOrDefault(0); + + public new virtual DateTime RevisedDate => base.RevisedDate.GetValueOrDefault(DateTime.MinValue); + + public virtual IEnumerable Revisions => throw new NotImplementedException(); + + public virtual IWorkItemType Type => _type ?? throw new NotImplementedException(); + + public abstract Uri Uri { get; } + + public virtual void ApplyRules(bool doNotUpdateChangedBy = false) + { + throw new NotImplementedException(); + } + + public virtual void Close() + { + throw new NotImplementedException(); + } + + public virtual IWorkItem Copy() + { + throw new NotImplementedException(); + } + + public virtual IHyperlink CreateHyperlink(string location) + { + throw new NotImplementedException(); + } + + public virtual IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) + { + throw new NotImplementedException(); + } + + public virtual bool IsValid() + { + throw new NotImplementedException(); + } + + public virtual void Open() + { + throw new NotImplementedException(); + } + + public virtual void PartialOpen() + { + throw new NotImplementedException(); + } + + public virtual void Reset() + { + throw new NotImplementedException(); + } + + public virtual void Save() + { + throw new NotImplementedException(); + } + + public virtual void Save(SaveFlags saveFlags) + { + throw new NotImplementedException(); + } + + public virtual IEnumerable Validate() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs new file mode 100644 index 00000000..6079ee17 --- /dev/null +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -0,0 +1,89 @@ +using System; + +namespace Microsoft.Qwiq +{ + public abstract class WorkItemCommon : WorkItemCore, IWorkItemCommon + { + public virtual int? AreaId + { + get => GetValue(CoreFieldRefNames.AreaId); + set => SetValue(CoreFieldRefNames.AreaId, value); + } + + public virtual string AreaPath + { + get => GetValue(CoreFieldRefNames.AreaPath); + set => SetValue(CoreFieldRefNames.AreaPath, value); + } + + public virtual string AssignedTo + { + get => GetValue(CoreFieldRefNames.AssignedTo); + set => SetValue(CoreFieldRefNames.AssignedTo, value); + } + + public virtual int? AttachedFileCount => GetValue(CoreFieldRefNames.AttachedFileCount); + + public virtual string ChangedBy => GetValue(CoreFieldRefNames.ChangedBy); + + public virtual DateTime? ChangedDate => GetValue(CoreFieldRefNames.ChangedDate); + + public virtual string CreatedBy => GetValue(CoreFieldRefNames.CreatedBy); + + public virtual DateTime? CreatedDate => GetValue(CoreFieldRefNames.CreatedDate); + + public virtual string Description + { + get => GetValue(CoreFieldRefNames.Description); + set => SetValue(CoreFieldRefNames.Description, value); + } + + public virtual int? ExternalLinkCount => GetValue(CoreFieldRefNames.ExternalLinkCount); + + public virtual string History + { + get => GetValue(CoreFieldRefNames.History) as string ?? string.Empty; + set => SetValue(CoreFieldRefNames.History, value); + } + + public virtual int? HyperLinkCount => GetValue(CoreFieldRefNames.HyperLinkCount); + + public virtual int? IterationId + { + get => GetValue(CoreFieldRefNames.IterationId); + set => SetValue(CoreFieldRefNames.IterationId, value); + } + + public virtual string IterationPath + { + get => GetValue(CoreFieldRefNames.IterationPath); + set => SetValue(CoreFieldRefNames.IterationPath, value); + } + + public virtual int? RelatedLinkCount => GetValue(CoreFieldRefNames.RelatedLinkCount); + + public virtual DateTime? RevisedDate => GetValue(CoreFieldRefNames.RevisedDate); + + public virtual string State + { + get => GetValue(CoreFieldRefNames.State); + set => SetValue(CoreFieldRefNames.State, value); + } + + public virtual string Tags + { + get => GetValue(CoreFieldRefNames.Tags); + set => SetValue(CoreFieldRefNames.Tags, value); + } + + public virtual string Title + { + get => GetValue(CoreFieldRefNames.Title); + set => SetValue(CoreFieldRefNames.Title, value); + } + + public virtual int? Watermark => GetValue(CoreFieldRefNames.Watermark); + + public virtual string WorkItemType => GetValue(CoreFieldRefNames.WorkItemType); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs new file mode 100644 index 00000000..9ea208b8 --- /dev/null +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -0,0 +1,48 @@ +using System; + +namespace Microsoft.Qwiq +{ + public abstract class WorkItemCore : IWorkItemCore + { + public virtual int? Id => GetValue(CoreFieldRefNames.Id); + + public virtual int? Rev => GetValue(CoreFieldRefNames.Rev); + + public abstract string Url { get; } + + /// + /// Gets or sets the with the specified name. + /// + /// + /// The . + /// + /// The name. + /// + /// + /// name is null + /// + public object this[string name] + { + get + + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return GetValue(name); + } + set + { + if (name == null) throw new ArgumentNullException(nameof(name)); + SetValue(name, value); + } + } + + protected virtual T GetValue(string name) + { + return TypeParser.Default.Parse(GetValue(name)); + } + + protected abstract object GetValue(string name); + + protected abstract void SetValue(string name, object value); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkType.cs b/src/Qwiq.Core/WorkItemLinkType.cs index 83206400..20eaa033 100644 --- a/src/Qwiq.Core/WorkItemLinkType.cs +++ b/src/Qwiq.Core/WorkItemLinkType.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkType : IWorkItemLinkType, IComparable, IEquatable + public class WorkItemLinkType : IWorkItemLinkType, IEquatable { private readonly Lazy _forwardFac; @@ -48,11 +48,6 @@ public override bool Equals(object obj) return WorkItemLinkTypeComparer.Instance.Equals(this, obj as IWorkItemLinkType); } - public int CompareTo(IWorkItemLinkType other) - { - return WorkItemLinkTypeComparer.Instance.Compare(this, other); - } - public bool Equals(IWorkItemLinkType other) { return WorkItemLinkTypeComparer.Instance.Equals(this, other); diff --git a/src/Qwiq.Mapper/IIdentifiable.cs b/src/Qwiq.Mapper/IIdentifiable.cs index bbc88379..50935bde 100644 --- a/src/Qwiq.Mapper/IIdentifiable.cs +++ b/src/Qwiq.Mapper/IIdentifiable.cs @@ -1,8 +1,10 @@ +using System; + namespace Microsoft.Qwiq.Mapper { - public interface IIdentifiable + [Obsolete("This interface has been deprecated and will be removed in a future version. See Microsoft.Qwiq.IIdentifiable`1")] + public interface IIdentifiable : IIdentifiable { - int Id { get; } } } diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 1fe5ee70..58e0c818 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -208,12 +208,10 @@ - - diff --git a/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs b/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs index f617a1dd..8c5d1ed4 100644 --- a/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs +++ b/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; -using Microsoft.Qwiq.Tests.Common; -using Microsoft.TeamFoundation.WorkItemTracking.Common.Constants; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -17,6 +16,8 @@ public abstract class IntegrationContextSpecificationSpecification : WorkItemSto [TestMethod] [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] public void AreaPath_is_equal() { RestResult.WorkItem.AreaPath.ShouldEqual(SoapResult.WorkItem.AreaPath); @@ -40,7 +41,7 @@ public void ChangedBy_is_equal() [TestCategory("localOnly")] public void ChangedDate_is_equal() { - RestResult.WorkItem.ChangedDate.ShouldEqual(SoapResult.WorkItem.ChangedDate.ToLocalTime()); + RestResult.WorkItem.ChangedDate.ShouldEqual(SoapResult.WorkItem.ChangedDate.ToUniversalTime()); } public override void Cleanup() @@ -49,22 +50,26 @@ public override void Cleanup() RestResult?.Dispose(); } - protected static readonly string[] CoreFields = - { - "System.AreaPath", "System.AssignedTo", "System.AttachedFileCount", "System.ChangedBy", - "System.ChangedDate", "System.CreatedBy", "System.CreatedDate", "System.Description", - "System.ExternalLinkCount", "System.History", "System.HyperLinkCount", "System.Id", - "System.IterationPath", "System.RelatedLinkCount", "System.Rev", "System.RevisedDate", - "System.State", "System.Title", "System.WorkItemType", CoreFieldRefNames.Tags - }; - [TestMethod] [TestCategory("localOnly")] public void CoreFields_are_equal() { - foreach (var field in CoreFields) + var exceptions = new List(); + foreach (var field in CoreFieldRefNames.All) + { + try + { + RestResult.WorkItem[field].ShouldEqual(SoapResult.WorkItem[field], field); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + if (exceptions.Any()) { - RestResult.WorkItem[field].ShouldEqual(SoapResult.WorkItem[field], field); + throw new AggregateException(ShouldExtensions.EachToUsefulString(exceptions), exceptions); } } @@ -86,7 +91,7 @@ public void Rev_is_equal() [TestCategory("localOnly")] public void CreatedDate_is_equal() { - RestResult.WorkItem.CreatedDate.ShouldEqual(SoapResult.WorkItem.CreatedDate); + RestResult.WorkItem.CreatedDate.ShouldEqual(SoapResult.WorkItem.CreatedDate.ToUniversalTime()); } [TestMethod] diff --git a/test/Qwiq.Core.Tests/LinkIntegrationTests.cs b/test/Qwiq.Core.Tests/LinkIntegrationTests.cs new file mode 100644 index 00000000..b6a30e05 --- /dev/null +++ b/test/Qwiq.Core.Tests/LinkIntegrationTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Core.Tests +{ + public abstract class LinkIntegrationTests : WorkItemStoreComparisonContextSpecification + { + protected IWorkItem RestResult { get; private set; } + protected IWorkItem SoapResult { get; private set; } + + protected int Id { get; set; } + + public override void When() + { + RestResult = Rest.Query(Id); + SoapResult = Soap.Query(Id); + } + } + + [TestClass] + public class Given_a_workitem_with_links : LinkIntegrationTests + { + public override void Given() + { + base.Given(); + Id = 156027; + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + [ExpectedException(typeof(NotImplementedException), "This is not yet implemented in the REST client.")] + public void The_Links_are_equal() + { + RestResult.Links.ShouldContainOnly(SoapResult.Links); + } + } +} diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 75fa507d..384ae242 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -226,6 +226,7 @@ + @@ -239,6 +240,7 @@ + diff --git a/test/Qwiq.Core.Tests/SingleIdTests.cs b/test/Qwiq.Core.Tests/SingleIdTests.cs index 7164caa8..e78353d1 100644 --- a/test/Qwiq.Core.Tests/SingleIdTests.cs +++ b/test/Qwiq.Core.Tests/SingleIdTests.cs @@ -1,19 +1,45 @@ -using System.Text; -using System.Threading.Tasks; +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Should; + namespace Microsoft.Qwiq.Core.Tests { [TestClass] - public class SingleIdTests : IntegrationContextSpecificationSpecification + public class Given_a_WorkItem_from_each_client : IntegrationContextSpecificationSpecification + { + private const int Id = 10726528; + + public override void When() + { + SoapResult.WorkItem = TimedAction(() => SoapResult.WorkItemStore.Query(Id), "SOAP", "Query By Id"); + RestResult.WorkItem = TimedAction(() => RestResult.WorkItemStore.Query(Id), "REST", "Query By Id"); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void WorkItemType_is_equal() + { + RestResult.WorkItem.Type.ShouldEqual(SoapResult.WorkItem.Type); + } + } + + [TestClass] + public class Given_a_WorkItem_from_each_client_by_AsOf : IntegrationContextSpecificationSpecification { private const int Id = 10726528; public override void When() { - SoapResult.WorkItem = SoapResult.WorkItemStore.Query(Id); - RestResult.WorkItem = RestResult.WorkItemStore.Query(Id); + DateTime t = DateTime.UtcNow; + + SoapResult.WorkItem = TimedAction(() => SoapResult.WorkItemStore.Query(Id, t), "SOAP", "Query By Id"); + RestResult.WorkItem = TimedAction(() => RestResult.WorkItemStore.Query(Id, t), "REST", "Query By Id"); } + + } } \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/TypeParserTests.cs b/test/Qwiq.Core.Tests/TypeParserTests.cs similarity index 95% rename from test/Qwiq.Mapper.Tests/TypeParserTests.cs rename to test/Qwiq.Core.Tests/TypeParserTests.cs index aec2b8a4..44e8cc7a 100644 --- a/test/Qwiq.Mapper.Tests/TypeParserTests.cs +++ b/test/Qwiq.Core.Tests/TypeParserTests.cs @@ -2,11 +2,11 @@ using System.Xml; using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; -using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.Qwiq.Mapper.Tests +namespace Microsoft.Qwiq.Core.Tests { public abstract class TypeParserTestsContext : ContextSpecification { @@ -20,6 +20,22 @@ public override void Given() } } + [TestClass] + public class when_parsing_a_value_type_to_nullable_value_type : TypeParserTestsContext + { + public override void When() + { + Expected = (int?)1; + Actual = TypeParser.Parse(1L); + } + + [TestMethod] + public void value_is_converted() + { + Actual.ShouldEqual(Expected); + } + } + [TestClass] // ReSharper disable once InconsistentNaming public class when_parsing_an_enum_value : TypeParserTestsContext diff --git a/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs b/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs index 1b68523d..dbf12864 100644 --- a/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs +++ b/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs @@ -9,7 +9,7 @@ public class WiqlFlatQueryTests : IntegrationContextSpecificationSpecification { private const int Id = 10726528; - private static readonly string Wiql = $"SELECT {string.Join(", ", CoreFields)} FROM WorkItems WHERE [System.Id] = {Id}"; + private static readonly string Wiql = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems WHERE [System.Id] = {Id}"; public override void When() { diff --git a/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs index 4f496fe8..79bf60e3 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs @@ -1,6 +1,12 @@ using System; +using System.Diagnostics; +using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; +using Should.Core.Exceptions; namespace Microsoft.Qwiq.Core.Tests { @@ -15,8 +21,66 @@ public override void Given() var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - Soap = Microsoft.Qwiq.Soap.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Soap); - Rest = Microsoft.Qwiq.Rest.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Rest); + + Soap = TimedAction( + ()=> Microsoft.Qwiq.Soap.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Soap), + "SOAP", + "Create WIS"); + + + + Rest = TimedAction( + () => Microsoft.Qwiq.Rest.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Rest), + "REST", + "Create WIS"); + + } + + protected static T TimedAction(Func action, string category, string userMessage) + { + var start = Clock.GetTimestamp(); + try + { + return action(); + } + finally + { + var stop = Clock.GetTimestamp(); + Debug.Print("{0}: {1} {2}", category, Clock.GetTimeSpan(start, stop), userMessage); + } + } + } + + [TestClass] + public class Given_a_WorkItemStore_from_each_implementation : WorkItemStoreComparisonContextSpecification + { + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + [ExpectedException(typeof(EqualException), "No EqualityComparer for TfsCredentials")] + public void AuthorizedCredentials_are_equal() + { + Rest.AuthorizedCredentials.ShouldEqual(Soap.AuthorizedCredentials); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + [ExpectedException(typeof(NotImplementedException), "REST does not have an implementation")] + public void FieldDefinitions_are_equal() + { + Rest.FieldDefinitions.ShouldContainOnly(Soap.FieldDefinitions); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + public void UserSid_are_equal() + { + Rest.UserSid.ShouldEqual(Soap.UserSid); } } } \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 3aeef2b8..5bf1be1b 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -5,7 +5,6 @@ using Microsoft.Qwiq.Core.Tests.Mocks; using Microsoft.Qwiq.Soap; using Microsoft.Qwiq.Tests.Common; -using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -19,8 +18,8 @@ public class Given_SOAP_and_REST_workitemstore_implementations : WorkItemStoreCo { public override void When() { - SoapLinkTypes = Soap.WorkItemLinkTypes; - RestLinkTypes = Rest.WorkItemLinkTypes; + SoapLinkTypes = TimedAction(()=> Soap.WorkItemLinkTypes, "SOAP", "Work Item Link Types"); + RestLinkTypes = TimedAction(() => Rest.WorkItemLinkTypes, "REST", "Work Item Link Types"); } [TestMethod] diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index c9319093..3592f738 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -74,7 +74,6 @@ - diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index 00b54471..87ac6b6e 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -4,20 +4,14 @@ using System.Linq; using System.Runtime.Serialization.Formatters.Binary; -using Microsoft.Qwiq.Exceptions; - namespace Microsoft.Qwiq.Mocks { [Serializable] - public class MockWorkItem : IWorkItem + public class MockWorkItem : WorkItem, IWorkItem { - internal bool PartialOpenWasCalled = false; - private readonly ICollection _links; - - - private IWorkItemType _type; + private IFieldCollection _fields; - private IEnumerable _revisions; + internal bool PartialOpenWasCalled; [Obsolete( "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] @@ -44,22 +38,27 @@ public MockWorkItem(IDictionary fields) "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] public MockWorkItem(string workItemType = null, IDictionary fields = null) : this( - new MockWorkItemType( - workItemType ?? "Mock", - CoreFieldDefinitions.All.Union(fields?.Keys.Select(MockFieldDefinition.Create) ?? Enumerable.Empty())), - fields) + new MockWorkItemType( + workItemType ?? "Mock", + CoreFieldDefinitions.All.Union( + fields?.Keys.Select(MockFieldDefinition.Create) + ?? Enumerable.Empty())), + fields) { } public MockWorkItem(IWorkItemType type, IDictionary fields = null) - : this(type, fields?.Select(p => new MockField(type.FieldDefinitions[p.Key], p.Value, p.Value)) ?? Enumerable.Empty()) + : this( + type, + fields?.Select(p => new MockField(type.FieldDefinitions[p.Key], p.Value, p.Value)) + ?? Enumerable.Empty()) { } public MockWorkItem(IWorkItemType type, IEnumerable fields) + : base(type) { if (fields == null) throw new ArgumentNullException(nameof(fields)); - Type = type ?? throw new ArgumentNullException(nameof(type)); foreach (var field in fields) { @@ -68,110 +67,13 @@ public MockWorkItem(IWorkItemType type, IEnumerable fields) f.OriginalValue = field.OriginalValue; } - _links = new MockLinkCollection(); + Links = new MockLinkCollection(); + Uri = new Uri($"vstfs:///WorkItemTracking/WorkItem/{Id}"); + Url = Uri.ToString(); Revisions = Enumerable.Empty(); - ApplyRules(false); - } - - public string AreaPath - { - get => GetValue(CoreFieldRefNames.AreaPath); - set => SetValue(CoreFieldRefNames.AreaPath, value); - } - - public string AssignedTo - { - get => GetValue(CoreFieldRefNames.AssignedTo); - set => SetValue(CoreFieldRefNames.AssignedTo, value); - } - - public int AttachedFileCount - { - get => (int)GetValue(CoreFieldRefNames.AttachedFileCount); - set => SetValue(CoreFieldRefNames.AttachedFileCount, value); - } - - public IEnumerable Attachments => throw new NotImplementedException(); - - public string ChangedBy - { - get => GetValue(CoreFieldRefNames.ChangedBy); - set => SetValue(CoreFieldRefNames.ChangedBy, value); - } - - public DateTime ChangedDate - { - get => (DateTime)GetValue(CoreFieldRefNames.ChangedDate); - set => SetValue(CoreFieldRefNames.ChangedDate, value); - } - - public string CreatedBy - { - get => GetValue(CoreFieldRefNames.CreatedBy); - set => SetValue(CoreFieldRefNames.CreatedBy, value); - } - - public DateTime CreatedDate - { - get => (DateTime)GetValue(CoreFieldRefNames.CreatedDate); - set => SetValue(CoreFieldRefNames.CreatedDate, value); - } - - public string Description - { - get => GetValue(CoreFieldRefNames.Description); - set => SetValue(CoreFieldRefNames.Description, value); - } - - public int ExternalLinkCount - { - get => (int)GetValue(CoreFieldRefNames.ExternalLinkCount); - set => SetValue(CoreFieldRefNames.ExternalLinkCount, value); - } - - private IFieldCollection _fields; - - public IFieldCollection Fields => _fields ?? (_fields = new MockFieldCollection(Type.FieldDefinitions)); - - public string History - { - get => GetValue(CoreFieldRefNames.History) as string ?? string.Empty; - set => SetValue(CoreFieldRefNames.History, value); - } - - public int HyperLinkCount - { - get => GetValue(CoreFieldRefNames.HyperLinkCount); - set => SetValue(CoreFieldRefNames.HyperLinkCount, value); - } - - public int Id - { - get => ((int?)GetValue(CoreFieldRefNames.Id)).GetValueOrDefault(0); - set => SetValue(CoreFieldRefNames.Id, value); + ApplyRules(); } - public bool IsDirty - { - get { return Fields.Any(p => p.IsDirty); } - } - - public string IterationPath - { - get => GetValue(CoreFieldRefNames.IterationPath); - set => SetValue(CoreFieldRefNames.IterationPath, value); - } - - public string Keywords - { - get => GetValue(WorkItemFields.Keywords); - set => SetValue(WorkItemFields.Keywords, value); - } - - public ICollection Links { get; set; } - - public int RelatedLinkCount => Links.OfType().Count(); - public string ReproSteps { get => GetValue("Repro Steps"); @@ -182,71 +84,54 @@ public string ReproSteps } } - public long Rev + public new DateTime? ChangedDate { - get => GetValue(CoreFieldRefNames.Rev); - set => SetValue(CoreFieldRefNames.Rev, value); + get => base.ChangedDate; + set => this[CoreFieldRefNames.ChangedDate] = value; } - public DateTime RevisedDate - { - get => GetValue(CoreFieldRefNames.RevisedDate); - set => SetValue(CoreFieldRefNames.RevisedDate, value); - } + public sealed override IFieldCollection Fields => _fields + ?? (_fields = new MockFieldCollection(Type.FieldDefinitions)); - public long Revision + public new int Id { - get => (long)GetValue("Revision"); - set => SetValue("Revision", value); + get => base.Id; + set => this[CoreFieldRefNames.Id] = value; } - public IEnumerable Revisions + public override bool IsDirty { - get => _revisions; - set => _revisions = value; + get + { + return Fields.Any(p => p.IsDirty); + } } - public string State + public override string Keywords { - get => GetValue(CoreFieldRefNames.State); - set => SetValue(CoreFieldRefNames.State, value); + get => GetValue(WorkItemFields.Keywords); + set => SetValue(WorkItemFields.Keywords, value); } - public string Tags - { - get => GetValue(CoreFieldRefNames.Tags); - set => SetValue(CoreFieldRefNames.Tags, value); - } + public new ICollection Links { get; set; } - public string Title - { - get => GetValue(CoreFieldRefNames.Title); - set => SetValue(CoreFieldRefNames.Title, value); - } + public new int RelatedLinkCount => Links.OfType().Count(); - public IWorkItemType Type - { - get => _type; - set - { - _type = value; - SetValue(CoreFieldRefNames.WorkItemType, value.Name); - } - } + public override IEnumerable Revisions { get; } + + public override Uri Uri { get; } - public Uri Uri { get; set; } + public override string Url { get; } - public object this[string name] + public override void ApplyRules(bool doNotUpdateChangedBy = false) { - get => GetValue(name); - set => SetValue(name, value); } - public void Close() + public override void Close() { } - public IWorkItem Copy() + public override IWorkItem Copy() { using (var stream = new MemoryStream()) { @@ -260,78 +145,63 @@ public IWorkItem Copy() var link = newItem.CreateRelatedLink(this); newItem.Links.Add(link); + newItem.ApplyRules(); + return newItem; } } - public IHyperlink CreateHyperlink(string location) - { - throw new NotImplementedException(); - } - - public IRelatedLink CreateRelatedLink(IWorkItem target) - { - return CreateRelatedLink(new MockWorkItemStore().WorkItemLinkTypes.Single(s => s.ReferenceName == CoreLinkTypeReferenceNames.Related).ForwardEnd, target); - } - - public IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) - { - throw new NotImplementedException(); - } - - public bool IsValid() + public override bool IsValid() { return Validate() == null; } - public void Open() + public override void Open() { } - public void PartialOpen() + public override void PartialOpen() { PartialOpenWasCalled = true; } - public void Reset() + public override void Reset() { } - public void Save() + public override void Save() { } - public void Save(SaveFlags flags) + public override void Save(SaveFlags flags) { } - public IEnumerable Validate() + public override IEnumerable Validate() { var invalidFields = Fields.Where(p => !p.IsValid).Select(p => p).ToArray(); - return invalidFields.Any() - ? invalidFields - : null; + return invalidFields.Any() ? invalidFields : null; } - private T GetValue(string field) + [Obsolete( + "This method is deprecated and will be removed in a future version. See CreateRelatedLink(IWorkItemLinkTypeEnd, IWorkItem) instead.")] + public IRelatedLink CreateRelatedLink(IWorkItem target) { - return (T)GetValue(field); + return CreateRelatedLink( + new MockWorkItemStore().WorkItemLinkTypes + .Single(s => s.ReferenceName == CoreLinkTypeReferenceNames.Related) + .ForwardEnd, + target); } - private object GetValue(string field) + protected override object GetValue(string field) { - if (field == null) throw new ArgumentNullException(nameof(field)); return Fields[field].Value; } - private void SetValue(string field, object value) + protected override void SetValue(string field, object value) { - if (field == null) throw new ArgumentNullException(nameof(field)); Fields[field].Value = value; } - - public void ApplyRules(bool doNotUpdateChangedBy = false) - { - } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/ShouldExtensions.cs b/test/Qwiq.Tests.Common/ShouldExtensions.cs index 1c52008b..624fe001 100644 --- a/test/Qwiq.Tests.Common/ShouldExtensions.cs +++ b/test/Qwiq.Tests.Common/ShouldExtensions.cs @@ -61,7 +61,7 @@ public static void ShouldContainOnly(this IEnumerable collection, IEnumera } } - private static string EachToUsefulString(this IEnumerable enumerable) + public static string EachToUsefulString(this IEnumerable enumerable) { var sb = new StringBuilder(); sb.AppendLine("{"); @@ -87,7 +87,7 @@ private static string EachToUsefulString(this IEnumerable enumerable) return sb.ToString(); } - private static string ToUsefulString(this object obj) + public static string ToUsefulString(this object obj) { string str; if (obj == null) From 56ac929ba3f01932d68b709150bdc087640e69c7 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 4 Apr 2017 12:48:47 -0700 Subject: [PATCH 076/251] Update Try... semantics for IFieldCollection and IFieldDefinitionCollection The semantics and behavior of the wrapped TFS SOAP objects do not match expectations or conventions for .NET methods. Signature and behavior updated to match .NET conventions. --- src/Qwiq.Core.Soap/FieldCollection.cs | 19 +++++++++++++++++-- .../FieldDefinitionCollection.cs | 19 +++++++++++++++++-- src/Qwiq.Core/FieldDefinitionCollection.cs | 12 ++++++++---- src/Qwiq.Core/IFieldCollection.cs | 2 +- src/Qwiq.Core/IFieldDefinitionCollection.cs | 2 +- test/Qwiq.Mocks/MockFieldCollection.cs | 17 +++++++++-------- 6 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/Qwiq.Core.Soap/FieldCollection.cs b/src/Qwiq.Core.Soap/FieldCollection.cs index 0422e2e0..14a8f3b9 100644 --- a/src/Qwiq.Core.Soap/FieldCollection.cs +++ b/src/Qwiq.Core.Soap/FieldCollection.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -26,9 +27,23 @@ public bool Contains(string name) return _innerCollection.Contains(name); } - public IField TryGetById(int id) + public bool TryGetById(int id, out IField field) { - return ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection.TryGetById(id))); + try + { + var nativeField = _innerCollection.TryGetById(id); + if (nativeField != null) + { + field = ExceptionHandlingDynamicProxyFactory.Create(new Field(nativeField)); + return true; + } + } + catch (Exception) + { + } + + field = null; + return false; } public IField GetById(int id) diff --git a/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs index 1831712e..fe3cc9df 100644 --- a/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -26,9 +27,23 @@ public override bool Contains(string fieldName) return _innerCollection.Contains(fieldName); } - public override IFieldDefinition TryGetById(int id) + public override bool TryGetById(int id, out IFieldDefinition fieldDefinition) { - return ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(_innerCollection.TryGetById(id))); + try + { + var nativeFieldDefinition = _innerCollection.TryGetById(id); + if (nativeFieldDefinition != null) + { + fieldDefinition = ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(nativeFieldDefinition)); + return true; + } + } + catch (Exception) + { + } + + fieldDefinition = null; + return false; } public override IEnumerator GetEnumerator() diff --git a/src/Qwiq.Core/FieldDefinitionCollection.cs b/src/Qwiq.Core/FieldDefinitionCollection.cs index 341d80c9..da39d0ef 100644 --- a/src/Qwiq.Core/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core/FieldDefinitionCollection.cs @@ -61,11 +61,15 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - public virtual IFieldDefinition TryGetById(int id) + public virtual bool TryGetById(int id, out IFieldDefinition fieldDefinition) { - int index; - if (_fieldUsagesById.TryGetValue(id, out index)) return _list[index]; - return null; + if (_fieldUsagesById.TryGetValue(id, out int index)) + { + fieldDefinition = _list[index]; + return true; + } + fieldDefinition = null; + return false; } public override bool Equals(object obj) diff --git a/src/Qwiq.Core/IFieldCollection.cs b/src/Qwiq.Core/IFieldCollection.cs index 19f5ce2c..adc66e0b 100644 --- a/src/Qwiq.Core/IFieldCollection.cs +++ b/src/Qwiq.Core/IFieldCollection.cs @@ -7,7 +7,7 @@ public interface IFieldCollection : IReadOnlyCollection IField this[string name] { get; } bool Contains(string name); - IField TryGetById(int id); + bool TryGetById(int id, out IField field); IField GetById(int id); } diff --git a/src/Qwiq.Core/IFieldDefinitionCollection.cs b/src/Qwiq.Core/IFieldDefinitionCollection.cs index 17776d6a..249456d9 100644 --- a/src/Qwiq.Core/IFieldDefinitionCollection.cs +++ b/src/Qwiq.Core/IFieldDefinitionCollection.cs @@ -9,6 +9,6 @@ public interface IFieldDefinitionCollection : IEnumerable bool Contains(string fieldName); - IFieldDefinition TryGetById(int id); + bool TryGetById(int id, out IFieldDefinition fieldDefinition); } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockFieldCollection.cs b/test/Qwiq.Mocks/MockFieldCollection.cs index 826617f6..911d4165 100644 --- a/test/Qwiq.Mocks/MockFieldCollection.cs +++ b/test/Qwiq.Mocks/MockFieldCollection.cs @@ -42,8 +42,10 @@ public bool Contains(string name) public IField GetById(int id) { - var byId = TryGetById(id); - if (byId == null) throw new ArgumentException($"Field {id} does not exist.", nameof(id)); + if (!TryGetById(id, out IField byId)) + { + throw new ArgumentException($"Field {id} does not exist.", nameof(id)); + } return byId; } @@ -57,24 +59,23 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - public IField TryGetById(int id) + public bool TryGetById(int id, out IField field) { - IField field; - if (_cache.TryGetValue(id, out field)) return field; + if (_cache.TryGetValue(id, out field)) return true; try { - var def = _definitions.TryGetById(id); - if (def != null) + if (_definitions.TryGetById(id, out IFieldDefinition def)) { field = new MockField(def); _cache[id] = field; + return true; } } // REVIEW: Catch a more specific exception catch (Exception) { } - return field; + return false; } } } \ No newline at end of file From 418aa3fe44cd2b15993439267364bf323c196cbe Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 5 Apr 2017 20:13:34 -0700 Subject: [PATCH 077/251] Update .gitignore file --- .gitignore | 198 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 158 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index c71c6217..1a4c6040 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,64 @@ + +# Created by https://www.gitignore.io/api/windows,visualstudio + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user +*.userosscache *.sln.docstates +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ +[Rr]eleases/ x64/ -build/ +x86/ bld/ [Bb]in/ [Oo]bj/ +[Ll]og/ -# Roslyn cache directories -*.ide/ +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -#NUNIT +# NUNIT *.VisualState.xml TestResult.xml @@ -32,6 +67,12 @@ TestResult.xml [Rr]eleasePS/ dlldata.c +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + *_i.c *_p.c *_i.h @@ -64,14 +105,18 @@ _Chutzpah* ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap # TFS 2012 Local Workspace $tf/ @@ -84,7 +129,7 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding addin-in +# JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in @@ -93,9 +138,14 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# Visual Studio code coverage results +*.coverage +*.coveragexml + # NCrunch _NCrunch_* .*crunch*.local.xml +nCrunchTemp_* # MightyMoose *.mm.* @@ -123,42 +173,62 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -## TODO: Comment the next line if you want to checkin your -## web deploy settings but do note that will include unencrypted -## passwords -#*.pubxml - -# NuGet Packages Directory -packages/* -## TODO: If the tool you use requires repositories.config -## uncomment the next line -!*/packages/repositories.config - -# Enable "build/" folder in the NuGet Packages folder since -# NuGet packages use it for MSBuild targets. -# This line needs to be after the ignore of the build folder -# (and the packages folder if the line above has been uncommented) -!packages/build/ - -# Windows Azure Build Output +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ # Others -sql/ -*.Cache ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview +*.jfm *.pfx *.publishsettings -node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ # RIA/Silverlight projects Generated_Code/ @@ -174,6 +244,7 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf +*.ndf # Business Intelligence projects *.rdl.data @@ -183,18 +254,65 @@ UpgradeLog*.htm # Microsoft Fakes FakesAssemblies/ -# LightSwitch generated files -GeneratedArtifacts/ -_Pvt_Extensions/ -ModelManifest.xml +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ -# nCrunch -*.ncrunchsolution -*.ncrunchproject +# Typescript v1 declaration files +typings/ -# NuGet -*.nupkg -*.nugetreferenceswitcher +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# End of https://www.gitignore.io/api/windows,visualstudio -#tools -.tools/ +# Visual Studio Workspace Bootstrapper +.tools/ \ No newline at end of file From 745c28715312f9fac387c3068a91490159970fcb Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 5 Apr 2017 20:22:46 -0700 Subject: [PATCH 078/251] Code clean up --- Qwiq.sln.DotSettings | 2 +- src/Qwiq.Core.Rest/Project.cs | 7 +- src/Qwiq.Core.Rest/Query.cs | 26 ++-- src/Qwiq.Core.Rest/TeamFoundationIdentity.cs | 26 +++- src/Qwiq.Core.Rest/VssConnectionAdapter.cs | 2 +- src/Qwiq.Core.Rest/WorkItem.cs | 23 +--- src/Qwiq.Core.Rest/WorkItemStore.cs | 59 ++++----- src/Qwiq.Core.Soap/Attachment.cs | 2 +- src/Qwiq.Core.Soap/CommonStructureService.cs | 25 ++-- src/Qwiq.Core.Soap/ExternalLink.cs | 9 +- src/Qwiq.Core.Soap/Field.cs | 33 +++-- src/Qwiq.Core.Soap/FieldCollection.cs | 45 ++++--- src/Qwiq.Core.Soap/FieldConflict.cs | 8 +- src/Qwiq.Core.Soap/FieldDefinition.cs | 7 +- .../FieldDefinitionCollection.cs | 7 +- src/Qwiq.Core.Soap/Hyperlink.cs | 2 +- src/Qwiq.Core.Soap/IRevision.cs | 49 +++++++ src/Qwiq.Core.Soap/IdentityDescriptor.cs | 6 +- .../IdentityManagementService.cs | 4 +- .../ItemAlreadyUpdatedOnServerException.cs | 2 +- src/Qwiq.Core.Soap/Link.cs | 6 +- src/Qwiq.Core.Soap/LinkCollection.cs | 3 +- src/Qwiq.Core.Soap/LinkHelper.cs | 2 +- src/Qwiq.Core.Soap/LinkMapper.cs | 4 +- src/Qwiq.Core.Soap/LinkTypeEndMapper.cs | 4 +- src/Qwiq.Core.Soap/Node.cs | 2 + src/Qwiq.Core.Soap/NodeInfo.cs | 4 +- src/Qwiq.Core.Soap/Project.cs | 12 +- src/Qwiq.Core.Soap/ProjectInfo.cs | 4 +- src/Qwiq.Core.Soap/ProjectProperty.cs | 4 +- src/Qwiq.Core.Soap/Query.cs | 20 ++- src/Qwiq.Core.Soap/QueryFactory.cs | 14 +- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 2 + .../RegisteredLinkTypeMapper.cs | 2 +- src/Qwiq.Core.Soap/RelatedLink.cs | 9 +- src/Qwiq.Core.Soap/Revision.cs | 55 ++++---- src/Qwiq.Core.Soap/TeamFoundationIdentity.cs | 72 ++++++----- .../TfsTeamProjectCollection.cs | 94 +++++++++++--- src/Qwiq.Core.Soap/ValidationException.cs | 2 +- src/Qwiq.Core.Soap/WorkItem.cs | 30 ++--- src/Qwiq.Core.Soap/WorkItemStore.cs | 77 ++++++++--- src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 1 - src/Qwiq.Core.Soap/WorkItemTypeCollection.cs | 32 +++++ src/Qwiq.Core/ClientType.cs | 23 +++- .../Credentials/AuthenticationOptions.cs | 2 +- .../Credentials/CredentialsNotifications.cs | 2 +- src/Qwiq.Core/Credentials/TfsCredentials.cs | 46 +------ .../Credentials/VssCredentialsComparer.cs | 51 ++++++++ src/Qwiq.Core/EqualityComparerFactory.cs | 31 ----- src/Qwiq.Core/FieldCollection.cs | 90 +++++++++++++ src/Qwiq.Core/FieldDefinition.cs | 9 +- src/Qwiq.Core/IField.cs | 45 +++++++ src/Qwiq.Core/IIdentifiable.cs | 2 +- src/Qwiq.Core/IProject.cs | 6 +- src/Qwiq.Core/IProjectCollection.cs | 12 ++ src/Qwiq.Core/IQuery.cs | 22 +++- src/Qwiq.Core/IQueryFactory.cs | 29 ++++- src/Qwiq.Core/IRevision.cs | 86 +++++++++---- .../ITeamFoundationIdentity.Extensions.cs | 41 +++--- src/Qwiq.Core/ITeamFoundationIdentity.cs | 81 +++++++++++- src/Qwiq.Core/ITfsTeamProjectCollection.cs | 16 ++- src/Qwiq.Core/IWorkItem.cs | 11 +- src/Qwiq.Core/IWorkItemStore.cs | 80 +++++++++++- src/Qwiq.Core/IWorkItemTypeCollection.cs | 9 ++ src/Qwiq.Core/IdentityDescriptorComparer.cs | 60 +++++++++ src/Qwiq.Core/Project.cs | 10 +- src/Qwiq.Core/ProjectCollection.cs | 73 +++++++++++ src/Qwiq.Core/Qwiq.Core.csproj | 11 +- src/Qwiq.Core/TeamFoundationIdentity.cs | 83 ++++++++++++ .../TeamFoundationIdentityComparer.cs | 44 +++++++ src/Qwiq.Core/WorkItem.cs | 112 ++++++++++++---- src/Qwiq.Core/WorkItemCommon.cs | 20 ++- src/Qwiq.Core/WorkItemComparer.cs | 120 ++++++++++++++++++ src/Qwiq.Core/WorkItemCore.cs | 59 ++++++++- src/Qwiq.Core/WorkItemLinkInfoComparer.cs | 6 +- src/Qwiq.Core/WorkItemTypeCollection.cs | 47 +++++++ .../IdentityManagementService.Extensions.cs | 9 +- ...ulkIdentityAwareAttributeMapperStrategy.cs | 19 +-- .../Mapper/IdentifiableComparer.cs | 52 ++++++++ .../Mapper/WorkItemKeyComparer.cs | 17 --- src/Qwiq.Identity/Qwiq.Identity.csproj | 2 +- .../IdentityDescriptorProxyTests.cs | 44 ------- .../IntegrationContextSpecification.cs | 7 +- test/Qwiq.Core.Tests/LinkIntegrationTests.cs | 78 ++++++------ .../Mocks/MockIdentityManagementService2.cs | 120 ++++++++++-------- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 19 ++- .../TimedContextSpecification.cs | 42 ++++++ test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs | 4 +- .../WiqlHierarchyQueryTests.cs | 12 +- ...ItemStoreComparisonContextSpecification.cs | 40 +++--- .../WorkItemStoreFactoryTests.cs | 33 ++--- test/Qwiq.Core.Tests/WorkItemStoreTests.cs | 7 +- test/Qwiq.Core.Tests/WorkItemTests.cs | 101 +++++++++++++++ test/Qwiq.Core.Tests/packages.config | 2 + ...entityManagementService.ExtensionsTests.cs | 8 +- .../Qwiq.Identity.Tests.csproj | 16 ++- test/Qwiq.Identity.Tests/packages.config | 2 + test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 16 ++- test/Qwiq.Linq.Tests/packages.config | 2 + .../Mocks/InstrumentedMockWorkItemStore.cs | 48 ++++--- .../Qwiq.Mapper.Tests.csproj | 10 ++ test/Qwiq.Mapper.Tests/packages.config | 2 + test/Qwiq.Mocks/MockField.cs | 56 ++++---- test/Qwiq.Mocks/MockFieldCollection.cs | 78 +----------- .../MockIdentityManagementService.cs | 113 +---------------- test/Qwiq.Mocks/MockProject.cs | 22 ++-- test/Qwiq.Mocks/MockTeamFoundationIdentity.cs | 72 ++++++----- test/Qwiq.Mocks/MockWorkItem.cs | 2 +- test/Qwiq.Mocks/MockWorkItemStore.cs | 14 +- .../Qwiq.Relatives.Tests.csproj | 16 ++- test/Qwiq.Relatives.Tests/packages.config | 2 + .../Qwiq.Tests.Common.csproj | 11 +- test/Qwiq.Tests.Common/packages.config | 2 + 113 files changed, 2211 insertions(+), 1027 deletions(-) create mode 100644 src/Qwiq.Core.Soap/IRevision.cs create mode 100644 src/Qwiq.Core.Soap/WorkItemTypeCollection.cs create mode 100644 src/Qwiq.Core/Credentials/VssCredentialsComparer.cs delete mode 100644 src/Qwiq.Core/EqualityComparerFactory.cs create mode 100644 src/Qwiq.Core/FieldCollection.cs create mode 100644 src/Qwiq.Core/IProjectCollection.cs create mode 100644 src/Qwiq.Core/IWorkItemTypeCollection.cs create mode 100644 src/Qwiq.Core/IdentityDescriptorComparer.cs create mode 100644 src/Qwiq.Core/ProjectCollection.cs create mode 100644 src/Qwiq.Core/TeamFoundationIdentity.cs create mode 100644 src/Qwiq.Core/TeamFoundationIdentityComparer.cs create mode 100644 src/Qwiq.Core/WorkItemComparer.cs create mode 100644 src/Qwiq.Core/WorkItemTypeCollection.cs create mode 100644 src/Qwiq.Identity/Mapper/IdentifiableComparer.cs delete mode 100644 src/Qwiq.Identity/Mapper/WorkItemKeyComparer.cs delete mode 100644 test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs create mode 100644 test/Qwiq.Core.Tests/TimedContextSpecification.cs create mode 100644 test/Qwiq.Core.Tests/WorkItemTests.cs diff --git a/Qwiq.sln.DotSettings b/Qwiq.sln.DotSettings index 6e083e09..42a96e1a 100644 --- a/Qwiq.sln.DotSettings +++ b/Qwiq.sln.DotSettings @@ -1,2 +1,2 @@  - <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Project.cs b/src/Qwiq.Core.Rest/Project.cs index 17fdecb6..e72119b1 100644 --- a/src/Qwiq.Core.Rest/Project.cs +++ b/src/Qwiq.Core.Rest/Project.cs @@ -11,17 +11,14 @@ internal class Project : Qwiq.Project { internal Project(TeamProjectReference project, WorkItemStore store) : base( - // REST API stores ID as GUID rather than INT - // Converting from 128-bit GUID will have some loss in precision - BitConverter.ToInt32(project.Id.ToByteArray(), 0), project.Id, project.Name, new Uri(project.Url), - new Lazy>( + new Lazy( () => { var wits = store.NativeWorkItemStore.Value.GetWorkItemTypesAsync(project.Name).GetAwaiter().GetResult(); - return wits.Select(s => new WorkItemType(s)); + return new WorkItemTypeCollection(wits.Select(s => new WorkItemType(s))); }), new Lazy>( () => diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 2be7c00b..886bffd5 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -9,7 +9,7 @@ namespace Microsoft.Qwiq.Rest { - public class Query : IQuery + internal class Query : IQuery { internal const int MaximumBatchSize = 200; @@ -107,29 +107,21 @@ public IEnumerable RunQuery() if (_ids == null) yield break; var expand = _fields != null ? (WorkItemExpand?)null : WorkItemExpand.All; - var qry = _ids.Partition(_workItemStore.BatchSize); + var qry = _ids.Partition(_workItemStore.PageSize); var ts = qry.Select(s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, _fields, _asOf, expand, WorkItemErrorPolicy.Omit)); - - // REST API does not return the WIT with the item - // Eagerly loading requires several trips to the server at a cost of 50-250ms for each project - - // REVIEW: Can we only request the projects needed instead of all? - var wits = new Lazy>>(() => _workItemStore.Projects.ToDictionary( - k => k.Name, - e => e.WorkItemTypes.ToDictionary( - i => i.Name, - j => j, - StringComparer.OrdinalIgnoreCase), - StringComparer.OrdinalIgnoreCase)); - + // This is done in parallel so keep performance similar to the SOAP client foreach (var workItem in Task.WhenAll(ts).GetAwaiter().GetResult().SelectMany(s => s.Select(f => f))) { + yield return ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(workItem, new Lazy( () => { - var proj = wits.Value[(string)workItem.Fields[CoreFieldRefNames.TeamProject]]; - var wit = proj[(string)workItem.Fields[CoreFieldRefNames.WorkItemType]]; + // REST API does not return the WIT with the item + // Eagerly loading requires several trips to the server at a cost of 50-2500ms for each trip + var proj = (string)workItem.Fields[CoreFieldRefNames.TeamProject]; + var witName = (string)workItem.Fields[CoreFieldRefNames.WorkItemType]; + var wit = _workItemStore.Projects[proj].WorkItemTypes[witName]; return wit; }))); } diff --git a/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs index 8c86d844..1f7f5f06 100644 --- a/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs @@ -7,16 +7,19 @@ namespace Microsoft.Qwiq.Rest { - public class TeamFoundationIdentity : ITeamFoundationIdentity + internal class TeamFoundationIdentity : ITeamFoundationIdentity { + private readonly Identity _identity; + private readonly Lazy _descriptor; private readonly Lazy> _memberOf; private readonly Lazy> _members; - public TeamFoundationIdentity(Identity identity) + internal TeamFoundationIdentity(Identity identity) { + _identity = identity; if (identity == null) throw new ArgumentNullException(nameof(identity)); DisplayName = identity.DisplayName; IsActive = identity.IsActive; @@ -60,5 +63,24 @@ public TeamFoundationIdentity(Identity identity) public string UniqueName { get; } public int UniqueUserId { get; } + + public string GetAttribute(string name, string defaultValue) + { + if (_identity.Properties.TryGetValue(name, out object obj)) + { + return obj.ToString(); + } + return defaultValue; + } + + public IEnumerable> GetProperties() + { + return _identity.Properties; + } + + public object GetProperty(string name) + { + return ExceptionHandlingDynamicProxyFactory.Create(_identity.Properties[name]); + } } } diff --git a/src/Qwiq.Core.Rest/VssConnectionAdapter.cs b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs index 06da1597..ae468391 100644 --- a/src/Qwiq.Core.Rest/VssConnectionAdapter.cs +++ b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs @@ -9,7 +9,7 @@ internal class VssConnectionAdapter : IInternalTfsTeamProjectCollection { private readonly VssConnection _connection; - public VssConnectionAdapter(VssConnection connection) + internal VssConnectionAdapter(VssConnection connection) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); AuthorizedCredentials = new TfsCredentials(connection.Credentials); diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index cfd60dac..c0c4c1d3 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -2,18 +2,22 @@ namespace Microsoft.Qwiq.Rest { - public class WorkItem : Qwiq.WorkItem + internal class WorkItem : Qwiq.WorkItem { private readonly TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem _item; private readonly Lazy _wit; + private readonly Lazy _fields; + internal WorkItem(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, Lazy wit) + :base(item.Fields) { _item = item; _wit = wit; Uri = new Uri(item.Url); Url = item.Url; + _fields = new Lazy(() => new FieldCollection(this, Type.FieldDefinitions, (r,d) => new Field(r,d))); } public override int Id => _item.Id.GetValueOrDefault(0); @@ -32,21 +36,6 @@ public override string Keywords public override string Url { get; } - protected override object GetValue(string field) - { - //if (!Type.FieldDefinitions.Contains(field)) - //{ - // // To preserve OM compatability - // throw new FieldDefinitionNotExistException( - // $"TF26026: A field definition ID {field} in the work item type definition file does not exist. Add a definition for this field ID, or remove the reference to the field ID and try again."); - //} - - return !_item.Fields.TryGetValue(field, out object val) ? null : val; - } - - protected override void SetValue(string field, object value) - { - _item.Fields[field] = value; - } + public override IFieldCollection Fields => _fields.Value; } } \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 2aad2471..54723222 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -16,25 +16,21 @@ internal class WorkItemStore : IWorkItemStore "(?.*)-(?.*)", RegexOptions.Singleline | RegexOptions.Compiled); - public int BatchSize { get; } - private readonly Lazy _linkTypes; + private readonly Lazy _projects; + private readonly Lazy _queryFactory; private readonly Lazy _tfs; - internal Lazy NativeWorkItemStore { get; } - - private readonly Lazy> _projects; - - private Lazy _fieldDefinitions; + private readonly Lazy _fieldDefinitions; internal WorkItemStore( Func tpcFactory, Func queryFactory, - int batchSize = Rest.Query.MaximumBatchSize) - : this(tpcFactory, () => tpcFactory()?.GetClient(), queryFactory, batchSize) + int pageSize = Rest.Query.MaximumBatchSize) + : this(tpcFactory, () => tpcFactory()?.GetClient(), queryFactory, pageSize) { } @@ -42,7 +38,7 @@ internal WorkItemStore( Func tpcFactory, Func wisFactory, Func queryFactory, - int batchSize = Rest.Query.MaximumBatchSize) + int pageSize = Rest.Query.MaximumBatchSize) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); @@ -53,10 +49,10 @@ internal WorkItemStore( // Boundary check the batch size - if (batchSize < Rest.Query.MinimumBatchSize) throw new PageSizeRangeException(); - if (batchSize > Rest.Query.MaximumBatchSize) throw new PageSizeRangeException(); + if (pageSize < Rest.Query.MinimumBatchSize || pageSize > Rest.Query.MaximumBatchSize) throw new PageSizeRangeException(); + - BatchSize = batchSize; + PageSize = pageSize; WorkItemLinkTypeCollection ValueFactory() { @@ -64,35 +60,37 @@ WorkItemLinkTypeCollection ValueFactory() } _linkTypes = new Lazy(ValueFactory); - _projects = new Lazy>( + _projects = new Lazy( () => { using (var projectHttpClient = _tfs.Value.GetClient()) { var projects = projectHttpClient.GetProjects(ProjectState.All).GetAwaiter().GetResult(); - return projects.Select(project => new Project(project, this)) - .Cast() - .ToList(); + return new ProjectCollection(projects.Select(project => new Project(project, this)).Cast().ToList()); } }); _fieldDefinitions = new Lazy(() => new FieldDefinitionCollection(this)); } + public int PageSize { get; } + + internal Lazy NativeWorkItemStore { get; } + public TfsCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; + public ClientType ClientType => ClientType.Rest; + public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions.Value; - public IEnumerable Projects => _projects.Value; + public IProjectCollection Projects => _projects.Value; public ITfsTeamProjectCollection TeamProjectCollection => _tfs.Value; public TimeZone TimeZone => TeamProjectCollection?.TimeZone ?? TimeZone.CurrentTimeZone; - // REVIEW: SOAP WorkItemStore gets the identity from its cache based on UserSid - public string UserDisplayName => throw new NotImplementedException(); + public string UserAccountName => TeamProjectCollection.AuthorizedIdentity.GetUserAlias(); - // REVIEW: SOAP WorkItemStore gets the identity from its cache based on UserSid - public string UserIdentityName => throw new NotImplementedException(); + public string UserDisplayName => TeamProjectCollection.AuthorizedIdentity.DisplayName; public string UserSid => TeamProjectCollection.AuthorizedIdentity.Descriptor.Identifier; @@ -168,13 +166,11 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo if (!forwardEnd.ReferenceName.EndsWith("Forward")) forwardEnd.ReferenceName += "-Forward"; type.SetForwardEnd(new WorkItemLinkTypeEnd(forwardEnd) { IsForwardLink = true, LinkType = type }); - type.SetReverseEnd(type.IsDirectional - ? new WorkItemLinkTypeEnd( - ends.SingleOrDefault( - p => p.ReferenceName.EndsWith("Reverse"))) - { LinkType = type } - : type.ForwardEnd); - + type.SetReverseEnd( + type.IsDirectional + ? new WorkItemLinkTypeEnd( + ends.SingleOrDefault(p => p.ReferenceName.EndsWith("Reverse"))) { LinkType = type } + : type.ForwardEnd); // The REST API does not return the ID of the link type. For well-known system links, we can populate the ID value if (CoreLinkTypeReferenceNames.All.Contains(type.ReferenceName, StringComparer.OrdinalIgnoreCase)) @@ -213,10 +209,7 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo private void Dispose(bool disposing) { - if (disposing) - { - if (NativeWorkItemStore.IsValueCreated) NativeWorkItemStore.Value?.Dispose(); - } + if (disposing) if (NativeWorkItemStore.IsValueCreated) NativeWorkItemStore.Value?.Dispose(); } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Attachment.cs b/src/Qwiq.Core.Soap/Attachment.cs index a72f607a..0103ed97 100644 --- a/src/Qwiq.Core.Soap/Attachment.cs +++ b/src/Qwiq.Core.Soap/Attachment.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq.Soap { - public class Attachment : IAttachment + internal class Attachment : IAttachment { private readonly Tfs.Attachment _attachment; diff --git a/src/Qwiq.Core.Soap/CommonStructureService.cs b/src/Qwiq.Core.Soap/CommonStructureService.cs index b6e33666..a13949e2 100644 --- a/src/Qwiq.Core.Soap/CommonStructureService.cs +++ b/src/Qwiq.Core.Soap/CommonStructureService.cs @@ -9,7 +9,7 @@ namespace Microsoft.Qwiq.Soap { - public class CommonStructureService : ICommonStructureService + internal class CommonStructureService : ICommonStructureService { private readonly Tfs.ICommonStructureService4 _service; @@ -23,29 +23,32 @@ public string CreateNode(string nodeName, string parentNodeUri, DateTime? startD return _service.CreateNode(nodeName, parentNodeUri, startDate, finishDate); } - public void SetIterationDates(string nodeUri, DateTime? startDate, DateTime? finishDate) + public XmlElement GetNodesXml(string[] nodeUris, bool childNodes) { - _service.SetIterationDates(nodeUri, startDate, finishDate); + return _service.GetNodesXml(nodeUris, childNodes); } public IProjectInfo GetProjectFromName(string projectName) { - return ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfo(_service.GetProjectFromName(projectName))); + return ExceptionHandlingDynamicProxyFactory.Create( + new ProjectInfo(_service.GetProjectFromName(projectName))); } - public IEnumerable ListStructures(string projectUri) + public IEnumerable ListAllProjects() { - return _service.ListStructures(projectUri).Select(i => ExceptionHandlingDynamicProxyFactory.Create(new NodeInfo(i))); + return _service.ListAllProjects() + .Select(p => ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfo(p))); } - public XmlElement GetNodesXml(string[] nodeUris, bool childNodes) + public IEnumerable ListStructures(string projectUri) { - return _service.GetNodesXml(nodeUris, childNodes); + return _service.ListStructures(projectUri) + .Select(i => ExceptionHandlingDynamicProxyFactory.Create(new NodeInfo(i))); } - public IEnumerable ListAllProjects() + public void SetIterationDates(string nodeUri, DateTime? startDate, DateTime? finishDate) { - return _service.ListAllProjects().Select(p => ExceptionHandlingDynamicProxyFactory.Create(new ProjectInfo(p))); + _service.SetIterationDates(nodeUri, startDate, finishDate); } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/ExternalLink.cs b/src/Qwiq.Core.Soap/ExternalLink.cs index 7f0074fa..b6123eee 100644 --- a/src/Qwiq.Core.Soap/ExternalLink.cs +++ b/src/Qwiq.Core.Soap/ExternalLink.cs @@ -4,14 +4,15 @@ namespace Microsoft.Qwiq.Soap { internal class ExternalLink : Link, IExternalLink { - internal ExternalLink(Tfs.ExternalLink externalLink) : base(externalLink) + internal ExternalLink(Tfs.ExternalLink externalLink) + : base(externalLink) { LinkedArtifactUri = externalLink.LinkedArtifactUri; ArtifactLinkTypeName = externalLink.ArtifactLinkType.Name; } - public string LinkedArtifactUri { get; } - public string ArtifactLinkTypeName { get; } + + public string LinkedArtifactUri { get; } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Field.cs b/src/Qwiq.Core.Soap/Field.cs index cca3843e..ab19a7db 100644 --- a/src/Qwiq.Core.Soap/Field.cs +++ b/src/Qwiq.Core.Soap/Field.cs @@ -1,41 +1,48 @@ +using System; + +using Microsoft.Qwiq.Exceptions; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { - internal class Field : IField + internal class Field : Qwiq.Field { private readonly Tfs.Field _field; internal Field(Tfs.Field field) + :base( + ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(field?.WorkItem)), + ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(field?.FieldDefinition))) { - _field = field; + _field = field ?? throw new ArgumentNullException(nameof(field)); } - public int Id => _field.Id; + public override int Id => _field.Id; - public bool IsChangedByUser => _field.IsChangedByUser; + public override bool IsChangedByUser => _field.IsChangedByUser; - public bool IsDirty => _field.IsDirty; + public override bool IsDirty => _field.IsDirty; - public bool IsEditable => _field.IsEditable; + public override bool IsEditable => _field.IsEditable; - public bool IsRequired => _field.IsRequired; + public override bool IsRequired => _field.IsRequired; - public bool IsValid => _field.IsValid; + public override bool IsValid => _field.IsValid; - public string Name => _field.Name; + public override string Name => _field.Name; - public string ReferenceName => _field.ReferenceName; + public override string ReferenceName => _field.ReferenceName; - public object OriginalValue + public override object OriginalValue { get => _field.OriginalValue; set => _field.Value = value; } - public ValidationState ValidationState => (ValidationState)(int)_field.Status; + public override ValidationState ValidationState => (ValidationState)(int)_field.Status; - public object Value + public override object Value { get => _field.Value; set => _field.Value = value; diff --git a/src/Qwiq.Core.Soap/FieldCollection.cs b/src/Qwiq.Core.Soap/FieldCollection.cs index 14a8f3b9..a6974a78 100644 --- a/src/Qwiq.Core.Soap/FieldCollection.cs +++ b/src/Qwiq.Core.Soap/FieldCollection.cs @@ -13,20 +13,39 @@ internal class FieldCollection : IFieldCollection { private readonly Tfs.FieldCollection _innerCollection; - public FieldCollection(Tfs.FieldCollection innerCollection) + internal FieldCollection(Tfs.FieldCollection innerCollection) { - _innerCollection = innerCollection; + _innerCollection = innerCollection ?? throw new ArgumentNullException(nameof(innerCollection)); } - public IField this[string name] => ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection[name])); - public int Count => _innerCollection.Count; + public IField this[string name] => ExceptionHandlingDynamicProxyFactory.Create( + new Field(_innerCollection[name])); + public bool Contains(string name) { return _innerCollection.Contains(name); } + public IField GetById(int id) + { + return ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection.GetById(id))); + } + + public IEnumerator GetEnumerator() + { + return _innerCollection.Cast() + .Select( + field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public bool TryGetById(int id, out IField field) { try @@ -45,21 +64,5 @@ public bool TryGetById(int id, out IField field) field = null; return false; } - - public IField GetById(int id) - { - return ExceptionHandlingDynamicProxyFactory.Create(new Field(_innerCollection.GetById(id))); - } - - public IEnumerator GetEnumerator() - { - return _innerCollection.Cast().Select(field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/FieldConflict.cs b/src/Qwiq.Core.Soap/FieldConflict.cs index 50fa3a45..c9269f83 100644 --- a/src/Qwiq.Core.Soap/FieldConflict.cs +++ b/src/Qwiq.Core.Soap/FieldConflict.cs @@ -1,14 +1,16 @@ +using System; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { - public class FieldConflict : IFieldConflict + internal class FieldConflict : IFieldConflict { private readonly Tfs.FieldConflict _field; - public FieldConflict(Tfs.FieldConflict field) + internal FieldConflict(Tfs.FieldConflict field) { - _field = field; + _field = field ?? throw new ArgumentNullException(nameof(field)); } public object BaselineValue => _field.BaselineValue; diff --git a/src/Qwiq.Core.Soap/FieldDefinition.cs b/src/Qwiq.Core.Soap/FieldDefinition.cs index 4ac571b9..68b38d34 100644 --- a/src/Qwiq.Core.Soap/FieldDefinition.cs +++ b/src/Qwiq.Core.Soap/FieldDefinition.cs @@ -7,11 +7,16 @@ namespace Microsoft.Qwiq.Soap internal class FieldDefinition : Qwiq.FieldDefinition { internal FieldDefinition(Tfs.FieldDefinition fieldDefinition) - :base(fieldDefinition?.ReferenceName, fieldDefinition?.Name) + :base(fieldDefinition?.Id ?? 0, fieldDefinition?.ReferenceName, fieldDefinition?.Name) { if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); } + /// + /// Performs an implicit conversion from to . + /// + /// The field definition. + /// The result of the conversion. public static implicit operator FieldDefinition(Tfs.FieldDefinition fieldDefinition) { return new FieldDefinition(fieldDefinition); diff --git a/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs index fe3cc9df..ad94ffc6 100644 --- a/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs @@ -14,13 +14,12 @@ internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection internal FieldDefinitionCollection(Tfs.FieldDefinitionCollection innerCollection) { - _innerCollection = innerCollection; + _innerCollection = innerCollection ?? throw new ArgumentNullException(nameof(innerCollection)); } public override int Count => _innerCollection.Count; - public override IFieldDefinition this[string name] => ExceptionHandlingDynamicProxyFactory - .Create(new FieldDefinition(_innerCollection[name])); + public override IFieldDefinition this[string name] => ExceptionHandlingDynamicProxyFactory.Create(_innerCollection[name]); public override bool Contains(string fieldName) { @@ -34,7 +33,7 @@ public override bool TryGetById(int id, out IFieldDefinition fieldDefinition) var nativeFieldDefinition = _innerCollection.TryGetById(id); if (nativeFieldDefinition != null) { - fieldDefinition = ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(nativeFieldDefinition)); + fieldDefinition = ExceptionHandlingDynamicProxyFactory.Create(new FieldDefinition(nativeFieldDefinition)); return true; } } diff --git a/src/Qwiq.Core.Soap/Hyperlink.cs b/src/Qwiq.Core.Soap/Hyperlink.cs index eface8a2..bc3a3b45 100644 --- a/src/Qwiq.Core.Soap/Hyperlink.cs +++ b/src/Qwiq.Core.Soap/Hyperlink.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Soap { - public class Hyperlink : Link, IHyperlink + internal class Hyperlink : Link, IHyperlink { private readonly Tfs.Hyperlink _hyperLink; diff --git a/src/Qwiq.Core.Soap/IRevision.cs b/src/Qwiq.Core.Soap/IRevision.cs new file mode 100644 index 00000000..b514fbf0 --- /dev/null +++ b/src/Qwiq.Core.Soap/IRevision.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Qwiq.Soap +{ + public interface IRevision + { + /// + /// Gets the attachments of the work item in this revision. + /// + IEnumerable Attachments { get; } + + /// + /// Gets the fields of the work item in this revision. + /// + IFieldCollection Fields { get; } + + /// + /// Gets the index of this revision. + /// + int Index { get; } + + /// + /// Gets the links of the work item in this revision. + /// + IEnumerable Links { get; } + + /// + /// Gets the work item that is stored in this revision. + /// + IWorkItem WorkItem { get; } + + /// + /// Gets the value of the specified field in the work item of this revision. + /// + /// The field of interest in the work item of this revision. + /// The value of the specified field. + object this[string name] { get; } + + /// + /// Gets the tagline for this revision. + /// + /// Returns System.String. + string GetTagLine(); + } +} diff --git a/src/Qwiq.Core.Soap/IdentityDescriptor.cs b/src/Qwiq.Core.Soap/IdentityDescriptor.cs index 258f13b7..ac758b10 100644 --- a/src/Qwiq.Core.Soap/IdentityDescriptor.cs +++ b/src/Qwiq.Core.Soap/IdentityDescriptor.cs @@ -1,14 +1,16 @@ +using System; + using Tfs = Microsoft.TeamFoundation.Framework.Client; namespace Microsoft.Qwiq.Soap { - public class IdentityDescriptor : IIdentityDescriptor + internal class IdentityDescriptor : IIdentityDescriptor { private readonly Tfs.IdentityDescriptor _descriptor; internal IdentityDescriptor(Tfs.IdentityDescriptor descriptor) { - _descriptor = descriptor; + _descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor)); } public string Identifier => _descriptor.Identifier; diff --git a/src/Qwiq.Core.Soap/IdentityManagementService.cs b/src/Qwiq.Core.Soap/IdentityManagementService.cs index 0c736922..a4d296e4 100644 --- a/src/Qwiq.Core.Soap/IdentityManagementService.cs +++ b/src/Qwiq.Core.Soap/IdentityManagementService.cs @@ -8,13 +8,13 @@ namespace Microsoft.Qwiq.Soap { - public class IdentityManagementService : IIdentityManagementService + internal class IdentityManagementService : IIdentityManagementService { private readonly Tfs.Client.IIdentityManagementService2 _identityManagementService2; internal IdentityManagementService(Tfs.Client.IIdentityManagementService2 identityManagementService2) { - _identityManagementService2 = identityManagementService2; + _identityManagementService2 = identityManagementService2 ?? throw new ArgumentNullException(nameof(identityManagementService2)); } public IEnumerable ReadIdentities(ICollection descriptors) diff --git a/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs b/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs index c16071aa..a03adb48 100644 --- a/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs +++ b/src/Qwiq.Core.Soap/ItemAlreadyUpdatedOnServerException.cs @@ -8,7 +8,7 @@ namespace Microsoft.Qwiq.Soap { - public class ItemAlreadyUpdatedOnServerException : InvalidOperationException + internal class ItemAlreadyUpdatedOnServerException : InvalidOperationException { private readonly Tfs.ItemAlreadyUpdatedOnServerException _exception; diff --git a/src/Qwiq.Core.Soap/Link.cs b/src/Qwiq.Core.Soap/Link.cs index defc1ea8..09c660c3 100644 --- a/src/Qwiq.Core.Soap/Link.cs +++ b/src/Qwiq.Core.Soap/Link.cs @@ -1,14 +1,16 @@ +using System; + using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { - public class Link : ILink + internal class Link : ILink { private readonly Tfs.Link _link; internal Link(Tfs.Link link) { - _link = link; + _link = link ?? throw new ArgumentNullException(nameof(link)); } public string Comment => _link.Comment; diff --git a/src/Qwiq.Core.Soap/LinkCollection.cs b/src/Qwiq.Core.Soap/LinkCollection.cs index 451dda37..26881ef4 100644 --- a/src/Qwiq.Core.Soap/LinkCollection.cs +++ b/src/Qwiq.Core.Soap/LinkCollection.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -14,7 +15,7 @@ internal class LinkCollection : ICollection internal LinkCollection(Tfs.WorkItem item) { - _item = item; + _item = item ?? throw new ArgumentNullException(nameof(item)); _linkHelper = new LinkHelper(); _linkMapper = new LinkMapper(); } diff --git a/src/Qwiq.Core.Soap/LinkHelper.cs b/src/Qwiq.Core.Soap/LinkHelper.cs index a464602d..07152156 100644 --- a/src/Qwiq.Core.Soap/LinkHelper.cs +++ b/src/Qwiq.Core.Soap/LinkHelper.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Soap { internal class LinkHelper { - public Tfs.Link FindEquivalentLink(Tfs.WorkItem item, ILink link) + internal Tfs.Link FindEquivalentLink(Tfs.WorkItem item, ILink link) { if (link.BaseType == BaseLinkType.RelatedLink) { diff --git a/src/Qwiq.Core.Soap/LinkMapper.cs b/src/Qwiq.Core.Soap/LinkMapper.cs index 4c459851..ffd1fd0d 100644 --- a/src/Qwiq.Core.Soap/LinkMapper.cs +++ b/src/Qwiq.Core.Soap/LinkMapper.cs @@ -8,7 +8,7 @@ namespace Microsoft.Qwiq.Soap { internal class LinkMapper { - public Tfs.Link Map(ILink link, Tfs.WorkItem item) + internal Tfs.Link Map(ILink link, Tfs.WorkItem item) { switch (link.BaseType) { @@ -31,7 +31,7 @@ public Tfs.Link Map(ILink link, Tfs.WorkItem item) } } - public ILink Map(Tfs.Link link) + internal ILink Map(Tfs.Link link) { switch (link.BaseType) { diff --git a/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs b/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs index ba1dceaf..7ecf5200 100644 --- a/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs +++ b/src/Qwiq.Core.Soap/LinkTypeEndMapper.cs @@ -1,12 +1,10 @@ using System.Linq; -using Microsoft.TeamFoundation.WorkItemTracking.Client; - namespace Microsoft.Qwiq.Soap { internal static class LinkTypeEndMapper { - public static TeamFoundation.WorkItemTracking.Client.WorkItemLinkTypeEnd Map(TeamFoundation.WorkItemTracking.Client.WorkItemStore store, IWorkItemLinkTypeEnd end) + internal static TeamFoundation.WorkItemTracking.Client.WorkItemLinkTypeEnd Map(TeamFoundation.WorkItemTracking.Client.WorkItemStore store, IWorkItemLinkTypeEnd end) { var linkType = store.WorkItemLinkTypes.Single(type => type.ReferenceName == end.LinkType.ReferenceName); return end.IsForwardLink ? linkType.ForwardEnd : linkType.ReverseEnd; diff --git a/src/Qwiq.Core.Soap/Node.cs b/src/Qwiq.Core.Soap/Node.cs index 687fd97d..b6305005 100644 --- a/src/Qwiq.Core.Soap/Node.cs +++ b/src/Qwiq.Core.Soap/Node.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -8,6 +9,7 @@ internal class Node : Qwiq.Node { internal Node(Tfs.Node node) { + if (node == null) throw new ArgumentNullException(nameof(node)); Id = node.Id; HasChildNodes = node.HasChildNodes; IsAreaNode = node.IsAreaNode; diff --git a/src/Qwiq.Core.Soap/NodeInfo.cs b/src/Qwiq.Core.Soap/NodeInfo.cs index a1bc94d6..c7dcee6b 100644 --- a/src/Qwiq.Core.Soap/NodeInfo.cs +++ b/src/Qwiq.Core.Soap/NodeInfo.cs @@ -1,3 +1,5 @@ +using System; + using Tfs = Microsoft.TeamFoundation.Server; namespace Microsoft.Qwiq.Soap @@ -8,7 +10,7 @@ internal class NodeInfo : INodeInfo internal NodeInfo(Tfs.NodeInfo nodeInfo) { - _nodeInfo = nodeInfo; + _nodeInfo = nodeInfo ?? throw new ArgumentNullException(nameof(nodeInfo)); } public string Uri diff --git a/src/Qwiq.Core.Soap/Project.cs b/src/Qwiq.Core.Soap/Project.cs index f4ce315a..783f767f 100644 --- a/src/Qwiq.Core.Soap/Project.cs +++ b/src/Qwiq.Core.Soap/Project.cs @@ -8,19 +8,14 @@ namespace Microsoft.Qwiq.Soap { - internal class Project : Qwiq.Project + internal class Project : Qwiq.Project, IIdentifiable { internal Project(Tfs.Project project) : base( - project.Id, project.Guid, project.Name, project.Uri, - new Lazy>( - () => project.WorkItemTypes.Cast() - .Select( - item => ExceptionHandlingDynamicProxyFactory.Create( - new WorkItemType(item)))), + new Lazy(() => new WorkItemTypeCollection(project.WorkItemTypes)), new Lazy>( () => project.AreaRootNodes.Cast() .Select( @@ -30,6 +25,9 @@ internal Project(Tfs.Project project) .Select( item => ExceptionHandlingDynamicProxyFactory.Create(new Node(item))))) { + Id = project.Id; } + + public int Id { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/ProjectInfo.cs b/src/Qwiq.Core.Soap/ProjectInfo.cs index 0f7b4539..dcb62035 100644 --- a/src/Qwiq.Core.Soap/ProjectInfo.cs +++ b/src/Qwiq.Core.Soap/ProjectInfo.cs @@ -1,3 +1,5 @@ +using System; + using Tfs = Microsoft.TeamFoundation.Server; namespace Microsoft.Qwiq.Soap @@ -8,7 +10,7 @@ internal class ProjectInfo : IProjectInfo internal ProjectInfo(Tfs.ProjectInfo projectInfo) { - _projectInfo = projectInfo; + _projectInfo = projectInfo ?? throw new ArgumentNullException(nameof(projectInfo)); } public string Uri { get => _projectInfo.Uri; diff --git a/src/Qwiq.Core.Soap/ProjectProperty.cs b/src/Qwiq.Core.Soap/ProjectProperty.cs index 3b6dc793..71392da1 100644 --- a/src/Qwiq.Core.Soap/ProjectProperty.cs +++ b/src/Qwiq.Core.Soap/ProjectProperty.cs @@ -1,3 +1,5 @@ +using System; + using Tfs = Microsoft.TeamFoundation.Server; namespace Microsoft.Qwiq.Soap @@ -8,7 +10,7 @@ internal class ProjectProperty : IProjectProperty internal ProjectProperty(Tfs.ProjectProperty projectProperty) { - _projectProperty = projectProperty; + _projectProperty = projectProperty ?? throw new ArgumentNullException(nameof(projectProperty)); } } } diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index 31d192a3..58ed3c9b 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -1,24 +1,34 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Qwiq.Exceptions; +using Microsoft.TeamFoundation.WorkItemTracking.Common; namespace Microsoft.Qwiq.Soap { internal class Query : IQuery { + private readonly int _pageSize; + private readonly TeamFoundation.WorkItemTracking.Client.Query _query; - internal Query(TeamFoundation.WorkItemTracking.Client.Query query) + internal Query(TeamFoundation.WorkItemTracking.Client.Query query, int pageSize = PageSizeLimits.DefaultPageSize) { - _query = query; + _pageSize = pageSize; + _query = query ?? throw new ArgumentNullException(nameof(query)); + + if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) + throw new PageSizeRangeException(); } public IEnumerable RunQuery() { - return _query.RunQuery() - .Cast() - .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(item))); + var wic = _query.RunQuery(); + wic.PageSize = _pageSize; + + return wic.Cast() + .Select(item => ExceptionHandlingDynamicProxyFactory.Create((WorkItem)item)); } public IEnumerable RunLinkQuery() diff --git a/src/Qwiq.Core.Soap/QueryFactory.cs b/src/Qwiq.Core.Soap/QueryFactory.cs index 3736c89b..208eeb7c 100644 --- a/src/Qwiq.Core.Soap/QueryFactory.cs +++ b/src/Qwiq.Core.Soap/QueryFactory.cs @@ -8,14 +8,14 @@ namespace Microsoft.Qwiq.Soap { internal class QueryFactory : IQueryFactory { - private readonly TeamFoundation.WorkItemTracking.Client.WorkItemStore _store; + private readonly WorkItemStore _store; - private QueryFactory(TeamFoundation.WorkItemTracking.Client.WorkItemStore store) + private QueryFactory(WorkItemStore store) { - _store = store; + _store = store ?? throw new ArgumentNullException(nameof(store)); } - public static IQueryFactory GetInstance(TeamFoundation.WorkItemTracking.Client.WorkItemStore store) + public static IQueryFactory GetInstance(WorkItemStore store) { return new QueryFactory(store); } @@ -23,7 +23,7 @@ public static IQueryFactory GetInstance(TeamFoundation.WorkItemTracking.Client.W public IQuery Create(string wiql, bool dayPrecision) { return ExceptionHandlingDynamicProxyFactory.Create( - new Query(new TeamFoundation.WorkItemTracking.Client.Query(_store, wiql, null, dayPrecision))); + new Query(new TeamFoundation.WorkItemTracking.Client.Query(_store.NativeWorkItemStore, wiql, null, dayPrecision), _store.PageSize)); } public IQuery Create(IEnumerable ids, string wiql) @@ -31,14 +31,14 @@ public IQuery Create(IEnumerable ids, string wiql) if (ids == null) throw new ArgumentNullException(nameof(ids)); if (string.IsNullOrWhiteSpace(wiql)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(wiql)); - return ExceptionHandlingDynamicProxyFactory.Create(new Query(new TeamFoundation.WorkItemTracking.Client.Query(_store, wiql, ids.ToArray()))); + return ExceptionHandlingDynamicProxyFactory.Create(new Query(new TeamFoundation.WorkItemTracking.Client.Query(_store.NativeWorkItemStore, wiql, ids.ToArray()), _store.PageSize)); } public IQuery Create(IEnumerable ids, DateTime? asOf = null) { // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). // It is used for ASOF - var wiql = "SELECT * FROM WorkItems"; + var wiql = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; if (asOf.HasValue) wiql += $" ASOF \'{asOf.Value:u}\'"; return Create(ids, wiql); } diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 291a7eac..9318a5ed 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -200,6 +200,7 @@ + @@ -228,6 +229,7 @@ + diff --git a/src/Qwiq.Core.Soap/RegisteredLinkTypeMapper.cs b/src/Qwiq.Core.Soap/RegisteredLinkTypeMapper.cs index 6ce9ad30..0c9970d7 100644 --- a/src/Qwiq.Core.Soap/RegisteredLinkTypeMapper.cs +++ b/src/Qwiq.Core.Soap/RegisteredLinkTypeMapper.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.Soap { internal static class RegisteredLinkTypeMapper { - public static Tfs.RegisteredLinkType Map(Tfs.WorkItemStore store, string linkTypeName) + internal static Tfs.RegisteredLinkType Map(Tfs.WorkItemStore store, string linkTypeName) => store.RegisteredLinkTypes .OfType() .FirstOrDefault(x => x.Name == linkTypeName); diff --git a/src/Qwiq.Core.Soap/RelatedLink.cs b/src/Qwiq.Core.Soap/RelatedLink.cs index 0ac3d989..60d92eb1 100644 --- a/src/Qwiq.Core.Soap/RelatedLink.cs +++ b/src/Qwiq.Core.Soap/RelatedLink.cs @@ -1,21 +1,26 @@ +using System; + using Microsoft.Qwiq.Exceptions; using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { - public class RelatedLink : Link, IRelatedLink + internal class RelatedLink : Link, IRelatedLink { private readonly Tfs.RelatedLink _relatedLink; + private readonly Lazy _linkTypeEnd; + internal RelatedLink(Tfs.RelatedLink relatedLink) : base(relatedLink) { _relatedLink = relatedLink; + _linkTypeEnd = new Lazy(()=> ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeEnd(_relatedLink.LinkTypeEnd))); } public int RelatedWorkItemId => _relatedLink.RelatedWorkItemId; - public IWorkItemLinkTypeEnd LinkTypeEnd => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemLinkTypeEnd(_relatedLink.LinkTypeEnd)); + public IWorkItemLinkTypeEnd LinkTypeEnd => _linkTypeEnd.Value; public string LinkSubType => _relatedLink.LinkTypeEnd.Name; } diff --git a/src/Qwiq.Core.Soap/Revision.cs b/src/Qwiq.Core.Soap/Revision.cs index 3ecf563f..ca6dc4a1 100644 --- a/src/Qwiq.Core.Soap/Revision.cs +++ b/src/Qwiq.Core.Soap/Revision.cs @@ -1,72 +1,74 @@ +using Microsoft.Qwiq.Exceptions; +using System; using System.Collections.Generic; using System.Linq; - -using Microsoft.Qwiq.Exceptions; - using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { /// - /// Wrapper around the TFS RevisionProxy. This exists so that every agent doesn't need to reference - /// all the TFS libraries. + /// Wrapper around the TFS RevisionProxy. This exists so that every agent doesn't need to reference + /// all the TFS libraries. /// - public class Revision : IRevision + internal class Revision : IRevision { private readonly Tfs.Revision _rev; + internal Revision(Tfs.Revision revision) { - _rev = revision; + _rev = revision ?? throw new ArgumentNullException(nameof(revision)); } /// - /// Gets the attachments of the work item in this revision. + /// Gets the attachments of the work item in this revision. /// public IEnumerable Attachments - { - get { return _rev.Attachments.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Attachment(item))); } - } - - /// - /// Gets the fields of the work item in this revision. - /// - public IDictionary Fields { get { - return _rev.Fields.Cast().ToDictionary(field => field.Name, field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))); + return _rev.Attachments.Cast() + .Select( + item => ExceptionHandlingDynamicProxyFactory.Create(new Attachment(item))); } } - + /// + /// Gets the fields of the work item in this revision. + /// + public IFieldCollection Fields => ExceptionHandlingDynamicProxyFactory.Create(new FieldCollection(_rev.Fields)); /// - /// Gets the index of this revision. + /// Gets the index of this revision. /// public int Index => _rev.Index; /// - /// Gets the links of the work item in this revision. + /// Gets the links of the work item in this revision. /// public IEnumerable Links { - get { return _rev.Links.Cast().Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Link(item))); } + get + { + return _rev.Links.Cast() + .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Link(item))); + } } /// - /// Gets the work item that is stored in this revision. + /// Gets the work item that is stored in this revision. /// - public IWorkItem WorkItem => ExceptionHandlingDynamicProxyFactory.Create(new WorkItem(_rev.WorkItem)); + public IWorkItem WorkItem => ExceptionHandlingDynamicProxyFactory + .Create(new WorkItem(_rev.WorkItem)); /// - /// Gets the value of the specified field in the work item of this revision. + /// Gets the value of the specified field in the work item of this revision. /// /// The field of interest in the work item of this revision. /// The value of the specified field. public object this[string name] => _rev[name]; /// - /// Gets the tagline for this revision. + /// Gets the tagline for this revision. /// /// Returns System.String. public string GetTagLine() @@ -74,5 +76,4 @@ public string GetTagLine() return _rev.GetTagLine(); } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/TeamFoundationIdentity.cs b/src/Qwiq.Core.Soap/TeamFoundationIdentity.cs index 7bf83d55..0510bc92 100644 --- a/src/Qwiq.Core.Soap/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core.Soap/TeamFoundationIdentity.cs @@ -8,58 +8,66 @@ namespace Microsoft.Qwiq.Soap { - public class TeamFoundationIdentity : ITeamFoundationIdentity + internal class TeamFoundationIdentity : ITeamFoundationIdentity { - private readonly Tfs.TeamFoundationIdentity _identity; - private readonly Lazy _descriptor; + private readonly Tfs.TeamFoundationIdentity _identity; + private readonly Lazy> _memberOf; private readonly Lazy> _members; internal TeamFoundationIdentity(Tfs.TeamFoundationIdentity identity) { - _identity = identity; - _descriptor = - new Lazy( - () => - ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptor(_identity?.Descriptor))); - - _memberOf = - new Lazy>( - () => - _identity?.MemberOf.Select( - item => - ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptor(item))) ?? Enumerable.Empty()); - - _members = - new Lazy>( - () => - _identity?.Members.Select( - item => - ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptor(item))) ?? Enumerable.Empty()); + _identity = identity ?? throw new ArgumentNullException(nameof(identity)); + + _descriptor = new Lazy( + () => ExceptionHandlingDynamicProxyFactory.Create( + new IdentityDescriptor(_identity.Descriptor))); + + _memberOf = new Lazy>( + () => _identity.MemberOf.Select( + item => ExceptionHandlingDynamicProxyFactory.Create( + new IdentityDescriptor(item)))); + + _members = new Lazy>( + () => _identity.Members.Select( + item => ExceptionHandlingDynamicProxyFactory.Create( + new IdentityDescriptor(item)))); } public IIdentityDescriptor Descriptor => _descriptor.Value; - public string DisplayName => _identity?.DisplayName; + public string DisplayName => _identity.DisplayName; - public bool IsActive => _identity?.IsActive ?? false; + public bool IsActive => _identity.IsActive; - public bool IsContainer => _identity?.IsContainer ?? false; + public bool IsContainer => _identity.IsContainer; public IEnumerable MemberOf => _memberOf.Value; public IEnumerable Members => _members.Value; - public Guid TeamFoundationId => _identity?.TeamFoundationId ?? Guid.Empty; + public Guid TeamFoundationId => _identity.TeamFoundationId; + + public string UniqueName => _identity.UniqueName; - public string UniqueName => _identity?.UniqueName; + public int UniqueUserId => _identity.UniqueUserId; - public int UniqueUserId => _identity?.UniqueUserId ?? -1; + public string GetAttribute(string name, string defaultValue) + { + return _identity.GetAttribute(name, defaultValue); + } + + public IEnumerable> GetProperties() + { + return _identity.GetProperties(); + } + + public object GetProperty(string name) + { + return ExceptionHandlingDynamicProxyFactory.Create(_identity.GetProperty(name)); + } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs index 9fad5957..8524bbbc 100644 --- a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs @@ -7,6 +7,9 @@ namespace Microsoft.Qwiq.Soap { + /// + /// + /// internal class TfsTeamProjectCollection : IInternalTfsTeamProjectCollection { private readonly Lazy _css; @@ -15,48 +18,105 @@ internal class TfsTeamProjectCollection : IInternalTfsTeamProjectCollection private readonly TeamFoundation.Client.TfsTeamProjectCollection _tfs; - internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection tfs) + /// + /// Initializes a new instance of the class. + /// + /// The TFS. + /// tfs + internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection teamProjectCollection) { - _tfs = tfs; + _tfs = teamProjectCollection ?? throw new ArgumentNullException(nameof(teamProjectCollection)); + + AuthorizedCredentials = new TfsCredentials(_tfs.ClientCredentials); + AuthorizedIdentity = new TeamFoundationIdentity(_tfs.AuthorizedIdentity); + Uri = _tfs.Uri; _css = new Lazy( () => ExceptionHandlingDynamicProxyFactory.Create( - new CommonStructureService(_tfs?.GetService()))); + new CommonStructureService(_tfs.GetService()))); _ims = new Lazy( () => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityManagementService(_tfs?.GetService()))); + new IdentityManagementService(_tfs.GetService()))); } - public TfsCredentials AuthorizedCredentials => new TfsCredentials(_tfs?.ClientCredentials); - - public ITeamFoundationIdentity AuthorizedIdentity => new TeamFoundationIdentity(_tfs?.AuthorizedIdentity); - + /// + /// Gets the credentials for this project collection. + /// + /// The authorized credentials. + public TfsCredentials AuthorizedCredentials { get; } + + /// + /// The identity who the calls to the server are being made for. + /// + /// The authorized identity. + public ITeamFoundationIdentity AuthorizedIdentity { get; } + + /// + /// Gets the common structure service. + /// + /// The common structure service. public ICommonStructureService CommonStructureService => _css.Value; - public bool HasAuthenticated => _tfs?.HasAuthenticated ?? false; + /// + /// Returns true if this object has successfully authenticated. + /// + /// true if this instance has authenticated; otherwise, false. + public bool HasAuthenticated => _tfs.HasAuthenticated; + /// + /// Gets the identity management service. + /// + /// The identity management service. public IIdentityManagementService IdentityManagementService => _ims.Value; - public Uri Uri => _tfs.Uri; - - public TimeZone TimeZone => _tfs?.TimeZone ?? TimeZone.CurrentTimeZone; - + /// + /// This is used to convert dates and times to UTC. + /// + /// The time zone. + public TimeZone TimeZone => _tfs.TimeZone; + + /// + /// The base url for this connection + /// + /// The URI. + public Uri Uri { get; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - public T GetService() + /// + /// Gets the client. + /// + /// + /// T. + public T GetClient() { - return _tfs == null ? default(T) : _tfs.GetService(); + return _tfs.GetClient(); } - public T GetClient() + /// + /// Gets the service. + /// + /// + /// T. + public T GetService() { - return _tfs.GetClient(); + return _tfs.GetService(); } + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// + /// true to release both managed and unmanaged resources; false to release only + /// unmanaged resources. + /// protected virtual void Dispose(bool disposing) { if (disposing) _tfs?.Dispose(); diff --git a/src/Qwiq.Core.Soap/ValidationException.cs b/src/Qwiq.Core.Soap/ValidationException.cs index 7913b630..91434a11 100644 --- a/src/Qwiq.Core.Soap/ValidationException.cs +++ b/src/Qwiq.Core.Soap/ValidationException.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.Soap { - public class ValidationException : InvalidOperationException + internal class ValidationException : InvalidOperationException { internal ValidationException(string message) : base(message) diff --git a/src/Qwiq.Core.Soap/WorkItem.cs b/src/Qwiq.Core.Soap/WorkItem.cs index 5ee1a519..e05b7c2b 100644 --- a/src/Qwiq.Core.Soap/WorkItem.cs +++ b/src/Qwiq.Core.Soap/WorkItem.cs @@ -12,17 +12,24 @@ namespace Microsoft.Qwiq.Soap /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference /// all the TFS libraries. /// - public class WorkItem : Qwiq.WorkItem, IWorkItem + internal class WorkItem : Qwiq.WorkItem, IWorkItem { private readonly Tfs.WorkItem _item; internal WorkItem(Tfs.WorkItem item) - : base(ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(item.Type))) + : base( + ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(item.Type)), + () => ExceptionHandlingDynamicProxyFactory.Create(new FieldCollection(item.Fields))) { _item = item; Url = item.Uri.ToString(); } + /// + /// Gets the integer that represents the revision number of this work item. + /// + public override int Revision => _item.Revision; + /// /// Gets or sets the string value of the AreaPath field for this work item. /// @@ -52,8 +59,6 @@ public override IEnumerable Attachments } } - - /// /// Gets the string value of the ChangedBy field for this work item. /// @@ -90,9 +95,6 @@ public override string Description /// public override int ExternalLinkCount => _item.ExternalLinkCount; - public override IFieldCollection Fields => ExceptionHandlingDynamicProxyFactory.Create( - new FieldCollection(_item.Fields)); - /// /// Gets or sets the string value of the History field for this work item. /// @@ -151,16 +153,11 @@ public override string Keywords /// public override DateTime RevisedDate => _item.RevisedDate; - /// - /// Gets the integer that represents the revision number of this work item. - /// - public override int Revision => _item.Revision; - /// /// Gets an object that represents a collection of valid revision numbers for this work /// item. /// - public override IEnumerable Revisions + public new IEnumerable Revisions { get { @@ -178,8 +175,6 @@ public override string State set => _item.State = value; } - - public override string Tags { get => _item.Tags; @@ -322,6 +317,11 @@ public override IEnumerable Validate() .Select(field => ExceptionHandlingDynamicProxyFactory.Create(new Field(field))); } + public static implicit operator WorkItem(Tfs.WorkItem workItem) + { + return new WorkItem(workItem); + } + /// /// Creates a copy of this WorkItem instance that is of the specified /// Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType. diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index 6b64c9cb..ad97aad7 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -1,15 +1,44 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.Client; +using Microsoft.TeamFoundation.WorkItemTracking.Common; using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Soap { + internal class ProjectCollection : IProjectCollection + { + private readonly TfsWorkItem.ProjectCollection _valueProjects; + + public ProjectCollection(TfsWorkItem.ProjectCollection valueProjects) + { + _valueProjects = valueProjects ?? throw new ArgumentNullException(nameof(valueProjects)); + } + + public IEnumerator GetEnumerator() + { + return _valueProjects.Cast() + .Select( + item => ExceptionHandlingDynamicProxyFactory.Create(new Project(item))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IProject IProjectCollection.this[string projectName] => ExceptionHandlingDynamicProxyFactory.Create(new Project(_valueProjects[projectName])); + + IProject IProjectCollection.this[Guid id] => ExceptionHandlingDynamicProxyFactory.Create(new Project(_valueProjects[id])); + } + /// /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference /// all the TFS libraries. @@ -24,27 +53,35 @@ internal class WorkItemStore : IWorkItemStore private readonly Lazy _workItemStore; + private readonly Lazy _projects; + internal WorkItemStore( TeamFoundation.Client.TfsTeamProjectCollection tfsNative, - Func queryFactory) + Func queryFactory, + int pageSize = PageSizeLimits.MaxPageSize) : this( () => ExceptionHandlingDynamicProxyFactory.Create( new TfsTeamProjectCollection(tfsNative)), - queryFactory) + queryFactory, pageSize) { } internal WorkItemStore( Func tpcFactory, Func wisFactory, - Func queryFactory) + Func queryFactory, + int pageSize = PageSizeLimits.MaxPageSize) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); + + if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) + throw new PageSizeRangeException(); + _tfs = new Lazy(tpcFactory); _workItemStore = new Lazy(wisFactory); - _queryFactory = new Lazy(() => queryFactory.Invoke(_workItemStore?.Value)); + _queryFactory = new Lazy(() => queryFactory(this)); _linkTypes = new Lazy( () => @@ -52,31 +89,33 @@ internal WorkItemStore( return new WorkItemLinkTypeCollection( _workItemStore.Value.WorkItemLinkTypes.Select(item => new WorkItemLinkType(item))); }); + + _projects = new Lazy(()=>new ProjectCollection(_workItemStore.Value.Projects)); + + PageSize = pageSize; } internal WorkItemStore( Func tpcFactory, - Func queryFactory) - : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory) + Func queryFactory, + int pageSize = PageSizeLimits.MaxPageSize) + : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory, pageSize) { } + public int PageSize { get; } + + public ClientType ClientType => ClientType.Soap; + public TfsCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; + internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; + public IFieldDefinitionCollection FieldDefinitions => ExceptionHandlingDynamicProxyFactory .Create( new FieldDefinitionCollection(_workItemStore.Value.FieldDefinitions)); - public IEnumerable Projects - { - get - { - return _workItemStore.Value.Projects.Cast() - .Select( - item => ExceptionHandlingDynamicProxyFactory.Create( - new Project(item))); - } - } + public IProjectCollection Projects => _projects.Value; public ITfsTeamProjectCollection TeamProjectCollection => _tfs.Value; @@ -84,7 +123,7 @@ public IEnumerable Projects public string UserDisplayName => _workItemStore.Value.UserDisplayName; - public string UserIdentityName => _workItemStore.Value.UserIdentityName; + public string UserAccountName => TeamProjectCollection.AuthorizedIdentity.GetUserAlias(); public string UserSid => _workItemStore.Value.UserSid; @@ -96,7 +135,7 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable Query(string wiql, bool dayPrecision = false) + public IEnumerable Query(string wiql, bool dayPrecision = true) { try { @@ -131,7 +170,7 @@ public IWorkItem Query(int id, DateTime? asOf = null) return Query(new[] { id }, asOf).SingleOrDefault(); } - public IEnumerable QueryLinks(string wiql, bool dayPrecision = false) + public IEnumerable QueryLinks(string wiql, bool dayPrecision = true) { try { diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 497930b7..72ecb870 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -5,7 +5,6 @@ using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Build.Client; -using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Soap diff --git a/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs b/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs new file mode 100644 index 00000000..f9f01da1 --- /dev/null +++ b/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs @@ -0,0 +1,32 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Qwiq.Exceptions; + +namespace Microsoft.Qwiq.Soap +{ + internal class WorkItemTypeCollection : IWorkItemTypeCollection + { + private readonly TeamFoundation.WorkItemTracking.Client.WorkItemTypeCollection _workItemTypeCollection; + + internal WorkItemTypeCollection(Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemTypeCollection workItemTypeCollection) + { + _workItemTypeCollection = workItemTypeCollection; + } + + public IEnumerator GetEnumerator() + { + return _workItemTypeCollection.Cast() + .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(item))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IWorkItemType this[string typeName] => ExceptionHandlingDynamicProxyFactory.Create(new WorkItemType(_workItemTypeCollection[typeName])); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/ClientType.cs b/src/Qwiq.Core/ClientType.cs index 576cb181..376b1f6e 100644 --- a/src/Qwiq.Core/ClientType.cs +++ b/src/Qwiq.Core/ClientType.cs @@ -1,11 +1,28 @@ namespace Microsoft.Qwiq { + /// + /// Enum ClientType + /// public enum ClientType : short { - Default = 0, + /// + /// No remote communications are made. + /// + None = 0, - Soap = 0, + /// + /// The default client type: . + /// + Default = Soap, - Rest = 1 + /// + /// The SOAP client integrates with Microsoft Team Foundation Server 2012 and later and Visual Studio Team Services via SOAP APIs. + /// + Soap = 1, + + /// + /// The REST client integrates with Team Foundation Server 2015 and later and Visual Studio Team Services via public REST APIs. + /// + Rest = 2 } } \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs index b6678b6c..0c895518 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs @@ -10,7 +10,7 @@ public class AuthenticationOptions public AuthenticationType AuthenticationType { get; } - public ClientType ClientType { get; } + public ClientType ClientType { get; set; } public AuthenticationOptions(string uri) : this(new Uri(uri, UriKind.Absolute)) diff --git a/src/Qwiq.Core/Credentials/CredentialsNotifications.cs b/src/Qwiq.Core/Credentials/CredentialsNotifications.cs index ca78cfb9..be134659 100644 --- a/src/Qwiq.Core/Credentials/CredentialsNotifications.cs +++ b/src/Qwiq.Core/Credentials/CredentialsNotifications.cs @@ -38,7 +38,7 @@ public CredentialsNotifications() var credential = n.Credentials; Trace.TraceInformation( - $"Connected to {n.TeamProjectCollection.Uri}: {n.TeamProjectCollection.AuthorizedIdentity.Descriptor.Identifier}"); + $"Connected to {n.TeamProjectCollection.Uri}: {n.TeamProjectCollection.AuthorizedIdentity.UniqueName}"); Trace.TraceInformation( "TFS connection attempt success with {0}/{1}.", diff --git a/src/Qwiq.Core/Credentials/TfsCredentials.cs b/src/Qwiq.Core/Credentials/TfsCredentials.cs index f9faa47a..7abbf7f4 100644 --- a/src/Qwiq.Core/Credentials/TfsCredentials.cs +++ b/src/Qwiq.Core/Credentials/TfsCredentials.cs @@ -34,52 +34,10 @@ public override int GetHashCode() } internal VssCredentials Credentials { get; } - } - - public class VssCredentialsComparer : GenericComparer - { - public static VssCredentialsComparer Instance => Nested.Instance; - - private VssCredentialsComparer() - { - } - - public override bool Equals(VssCredentials x, VssCredentials y) - { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - - return GenericComparer.Default.Equals(x.Windows, y.Windows) - && GenericComparer.Default.Equals(x.Federated, y.Federated); - } - - public override int GetHashCode(VssCredentials obj) - { - unchecked - { - var hash = 27; - hash = (13 * hash) ^ (obj.Windows != null ? obj.Windows.GetHashCode() : 0); - hash = (13 * hash) ^ (obj.Federated != null ? obj.Federated.GetHashCode() : 0); - - return hash; - } - } - - // ReSharper disable ClassNeverInstantiated.Local - private class Nested - // ReSharper restore ClassNeverInstantiated.Local + public static implicit operator TfsCredentials(VssCredentials credentials) { - // ReSharper disable MemberHidesStaticFromOuterClass - internal static readonly VssCredentialsComparer Instance = new VssCredentialsComparer(); - // ReSharper restore MemberHidesStaticFromOuterClass - - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static Nested() - { - } + return new TfsCredentials(credentials); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/VssCredentialsComparer.cs b/src/Qwiq.Core/Credentials/VssCredentialsComparer.cs new file mode 100644 index 00000000..37d26f88 --- /dev/null +++ b/src/Qwiq.Core/Credentials/VssCredentialsComparer.cs @@ -0,0 +1,51 @@ +using Microsoft.VisualStudio.Services.Common; + +namespace Microsoft.Qwiq.Credentials +{ + public class VssCredentialsComparer : GenericComparer + { + public static VssCredentialsComparer Instance => Nested.Instance; + + private VssCredentialsComparer() + { + } + + public override bool Equals(VssCredentials x, VssCredentials y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + + return GenericComparer.Default.Equals(x.Windows, y.Windows) + && GenericComparer.Default.Equals(x.Federated, y.Federated); + } + + public override int GetHashCode(VssCredentials obj) + { + unchecked + { + var hash = 27; + hash = (13 * hash) ^ (obj.Windows != null ? obj.Windows.GetHashCode() : 0); + hash = (13 * hash) ^ (obj.Federated != null ? obj.Federated.GetHashCode() : 0); + + return hash; + } + } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly VssCredentialsComparer Instance = new VssCredentialsComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/EqualityComparerFactory.cs b/src/Qwiq.Core/EqualityComparerFactory.cs deleted file mode 100644 index c97f3509..00000000 --- a/src/Qwiq.Core/EqualityComparerFactory.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Microsoft.Qwiq -{ - public static class EqualityComparerFactory - { - private class MyComparer : GenericComparer - { - private readonly Func _getHashCodeFunc; - private readonly Func _equalsFunc; - - public MyComparer(Func getHashCodeFunc, Func equalsFunc = null) - { - _getHashCodeFunc = getHashCodeFunc ?? throw new ArgumentNullException(nameof(getHashCodeFunc)); - _equalsFunc = equalsFunc; - } - - public override bool Equals(T x, T y) => _equalsFunc == null ? _equalsFunc(x, y) : base.Equals(x, y); - - public override int GetHashCode(T obj) => _getHashCodeFunc(obj); - } - - public static IEqualityComparer CreateComparer(Func getHashCodeFunc, Func equalsFunc = null) - { - - - return new MyComparer(getHashCodeFunc, equalsFunc); - } - } -} diff --git a/src/Qwiq.Core/FieldCollection.cs b/src/Qwiq.Core/FieldCollection.cs new file mode 100644 index 00000000..d6d5f5e9 --- /dev/null +++ b/src/Qwiq.Core/FieldCollection.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Qwiq +{ + public class FieldCollection : IFieldCollection + { + private readonly IDictionary _cache; + + private readonly IRevisionInternal _revision; + + private readonly IFieldDefinitionCollection _definitions; + + private readonly Func _fieldFactory; + + internal FieldCollection( + IRevisionInternal revision, + IFieldDefinitionCollection definitions, + Func fieldFactory) + { + _revision = revision; + _definitions = definitions; + _fieldFactory = fieldFactory; + _cache = new Dictionary(); + } + + public virtual int Count => _definitions.Count; + + public virtual IField this[string name] + { + get + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return GetById(_definitions[name].Id); + } + } + + public virtual bool Contains(string name) + { + try + { + return _definitions.Contains(name); + } + // REVIEW: Catch a more specific exception + catch (Exception) + { + return false; + } + } + + public virtual IField GetById(int id) + { + if (!TryGetById(id, out IField byId)) + { + throw new ArgumentException($"Field {id} does not exist.", nameof(id)); + } + return byId; + } + + public virtual IEnumerator GetEnumerator() + { + return _cache.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public virtual bool TryGetById(int id, out IField field) + { + if (_cache.TryGetValue(id, out field)) return true; + try + { + if (_definitions.TryGetById(id, out IFieldDefinition def)) + { + field = _fieldFactory(_revision, def); + _cache[id] = field; + return true; + } + } + // REVIEW: Catch a more specific exception + catch (Exception) + { + } + return false; + } + } +} diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs index bd28760b..889106e0 100644 --- a/src/Qwiq.Core/FieldDefinition.cs +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq /// /// A facade for /// - public class FieldDefinition : IFieldDefinition, IComparable, IEquatable + public class FieldDefinition : IFieldDefinition, IEquatable { internal FieldDefinition(int id, string referenceName, string name) : this(referenceName, name) @@ -23,7 +23,7 @@ internal FieldDefinition(string referenceName, string name) Name = name; ReferenceName = referenceName; - Id = name.GetHashCode() ^ referenceName.GetHashCode(); + Id = FieldDefinitionComparer.Instance.GetHashCode(this); } public string Name { get; } @@ -32,11 +32,6 @@ internal FieldDefinition(string referenceName, string name) public int Id { get; } - public int CompareTo(IFieldDefinition other) - { - return FieldDefinitionComparer.Instance.Compare(this, other); - } - public bool Equals(IFieldDefinition other) { return FieldDefinitionComparer.Instance.Equals(this, other); diff --git a/src/Qwiq.Core/IField.cs b/src/Qwiq.Core/IField.cs index 5448f88b..1dbea2e2 100644 --- a/src/Qwiq.Core/IField.cs +++ b/src/Qwiq.Core/IField.cs @@ -1,3 +1,5 @@ +using System; + namespace Microsoft.Qwiq { public interface IField @@ -14,4 +16,47 @@ public interface IField bool IsChangedByUser { get; } object Value { get; set; } } + + internal class Field : IField + { + private readonly IRevisionInternal _revision; + + private readonly IFieldDefinition _fieldDefinition; + + protected internal Field(IRevisionInternal revision, IFieldDefinition fieldDefinition) + { + _revision = revision ?? throw new ArgumentNullException(nameof(revision)); + _fieldDefinition = fieldDefinition ?? throw new ArgumentNullException(nameof(fieldDefinition)); + } + + public virtual bool IsValid => ValidationState == ValidationState.Valid; + + public virtual string Name => _fieldDefinition.Name; + + public virtual string ReferenceName => _fieldDefinition.ReferenceName; + + public virtual object OriginalValue + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public virtual ValidationState ValidationState => throw new NotImplementedException(); + + public virtual bool IsChangedByUser => throw new NotImplementedException(); + + public virtual object Value + { + get => _revision.GetCurrentFieldValue(_fieldDefinition); + set => _revision.SetFieldValue(_fieldDefinition, value); + } + + public virtual int Id => _fieldDefinition.Id; + + public virtual bool IsDirty => throw new NotImplementedException(); + + public virtual bool IsEditable => throw new NotImplementedException(); + + public virtual bool IsRequired => throw new NotImplementedException(); + } } diff --git a/src/Qwiq.Core/IIdentifiable.cs b/src/Qwiq.Core/IIdentifiable.cs index d2f38e80..06d21f58 100644 --- a/src/Qwiq.Core/IIdentifiable.cs +++ b/src/Qwiq.Core/IIdentifiable.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq /// This allows an object to be identified, K is the identifier (AKA Key) /// /// - public interface IIdentifiable + public interface IIdentifiable { TKey Id { get; } } diff --git a/src/Qwiq.Core/IProject.cs b/src/Qwiq.Core/IProject.cs index 086883c1..cca5d6d5 100644 --- a/src/Qwiq.Core/IProject.cs +++ b/src/Qwiq.Core/IProject.cs @@ -3,20 +3,18 @@ namespace Microsoft.Qwiq { - public interface IProject + public interface IProject { IEnumerable AreaRootNodes { get; } Guid Guid { get; } - int Id { get; } - IEnumerable IterationRootNodes { get; } string Name { get; } Uri Uri { get; } - IEnumerable WorkItemTypes { get; } + IWorkItemTypeCollection WorkItemTypes { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IProjectCollection.cs b/src/Qwiq.Core/IProjectCollection.cs new file mode 100644 index 00000000..154bc55c --- /dev/null +++ b/src/Qwiq.Core/IProjectCollection.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Qwiq +{ + public interface IProjectCollection : IEnumerable + { + IProject this[string projectName] { get; } + + IProject this[Guid id] { get; } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/IQuery.cs b/src/Qwiq.Core/IQuery.cs index 9a2623ce..630f2ec9 100644 --- a/src/Qwiq.Core/IQuery.cs +++ b/src/Qwiq.Core/IQuery.cs @@ -2,11 +2,27 @@ namespace Microsoft.Qwiq { + /// + /// Represents a query to the . + /// public interface IQuery { - IEnumerable RunQuery(); + /// + /// Gets a collection of objects associated with this query. + /// + /// IEnumerable<IWorkItemLinkTypeEnd>. + IEnumerable GetLinkTypes(); + + /// + /// Executes a query that gets a collection of objects that satisfy the WIQL. + /// + /// IEnumerable<IWorkItemLinkInfo>. IEnumerable RunLinkQuery(); - IEnumerable GetLinkTypes(); + /// + /// Executes a query that gets a collection of objects that satisfy the WIQL. + /// + /// IEnumerable<IWorkItem>. + IEnumerable RunQuery(); } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/IQueryFactory.cs b/src/Qwiq.Core/IQueryFactory.cs index 18a1fd4a..d97b86b6 100644 --- a/src/Qwiq.Core/IQueryFactory.cs +++ b/src/Qwiq.Core/IQueryFactory.cs @@ -3,13 +3,38 @@ namespace Microsoft.Qwiq { + /// + /// Initializes a new instance of . + /// internal interface IQueryFactory { + /// + /// Creates an instance of that is described in . + /// + /// The work item query string to execute. + /// + /// True to ignore time values so that DateTime objects are treated as dates; otherwise, + /// false. + /// + /// IQuery. IQuery Create(string wiql, bool dayPrecision = false); + /// + /// Create an instance of with a set of fields that is referred to in + /// that are specified by an ID number (). + /// + /// A collection of work item IDs. + /// WIQL definition of fields to return. + /// IQuery. IQuery Create(IEnumerable ids, string wiql); + /// + /// Create an instance of for work items specified by ID number () at an + /// optional previous date. + /// + /// A collection of work item IDs. + /// Optional: The date of the desired work item state. + /// IQuery Create(IEnumerable ids, DateTime? asOf = null); } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/IRevision.cs b/src/Qwiq.Core/IRevision.cs index 133937a9..cf3cee4c 100644 --- a/src/Qwiq.Core/IRevision.cs +++ b/src/Qwiq.Core/IRevision.cs @@ -1,46 +1,76 @@ +using System; using System.Collections.Generic; namespace Microsoft.Qwiq { - public interface IRevision + public interface IRevision : IWorkItemCore { - /// - /// Gets the attachments of the work item in this revision. - /// - IEnumerable Attachments { get; } - /// /// Gets the fields of the work item in this revision. /// - IDictionary Fields { get; } - - /// - /// Gets the index of this revision. - /// - int Index { get; } - - /// - /// Gets the links of the work item in this revision. - /// - IEnumerable Links { get; } - - /// - /// Gets the work item that is stored in this revision. - /// - IWorkItem WorkItem { get; } + IFieldCollection Fields { get; } /// /// Gets the value of the specified field in the work item of this revision. /// /// The field of interest in the work item of this revision. /// The value of the specified field. - object this[string name] { get; } + new object this[string name] { get; } - /// - /// Gets the tagline for this revision. - /// - /// Returns System.String. - string GetTagLine(); + + } + + internal class Revision : IRevision + { + private readonly Lazy _fields; + + private Dictionary _values; + + internal Revision( + IFieldDefinitionCollection definitions, + int revision, + Func fieldFactory) + { + Rev = revision; + _values = new Dictionary(); + _fields = new Lazy(() => fieldFactory(this, definitions)); + } + + internal Revision( + WorkItem workItem, + int revision) + { + WorkItem = workItem ?? throw new ArgumentNullException(nameof(workItem)); + Rev = revision; + _fields = new Lazy(() => WorkItem.Fields); + } + + public virtual object this[string name] + { + get + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return Fields[name].Value; + } + } + + public int? Rev { get; } + + object IWorkItemCore.this[string name] + { + get => this[name]; + set => throw new NotSupportedException(); + } + + private WorkItem WorkItem { get; } + + public IFieldCollection Fields => _fields.Value; + + + public int? Id => WorkItem?.Id; + + public string Url => WorkItem?.Url; } } + diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs index 2e819219..7ba99f51 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs @@ -1,34 +1,41 @@ using System; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq { // ReSharper disable InconsistentNaming public static class ITeamFoundationIdentityExtensions - // ReSharper restore InconsistentNaming + // ReSharper restore InconsistentNaming { public static string GetUserAlias(this ITeamFoundationIdentity identity) { - if (identity == null) throw new ArgumentNullException("identity"); + if (identity == null) throw new ArgumentNullException(nameof(identity)); - if (identity.Descriptor.Identifier.Contains("@")) - { - var identifier = identity.Descriptor.Identifier; - var identifierSplit = identifier.Split('\\'); + var alias = identity.GetAttribute(IdentityAttributeTags.AccountName, null) ?? GetUserAlias(identity.Descriptor); - if (identifierSplit.Length == 2) - { - return identifierSplit[1].Split('@')[0]; - } + if (!string.IsNullOrEmpty(alias)) + { + return alias; } - else + + var uniqueName = identity.UniqueName; + var uniqueNameSplit = uniqueName.Split('\\'); + return uniqueNameSplit.Length == 2 + ? uniqueNameSplit[1] + : null; + } + + public static string GetUserAlias(this IIdentityDescriptor descriptor) + { + if (!descriptor.Identifier.Contains("@")) return null; + var identifier = descriptor.Identifier; + var identifierSplit = identifier.Split('\\'); + + if (identifierSplit.Length == 2) { - var uniqueName = identity.UniqueName; - var uniqueNameSplit = uniqueName.Split('\\'); - if (uniqueNameSplit.Length == 2) - { - return uniqueNameSplit[1]; - } + return identifierSplit[1].Split('@')[0]; } return null; diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.cs b/src/Qwiq.Core/ITeamFoundationIdentity.cs index fbf81c06..79631daf 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.cs @@ -5,15 +5,92 @@ namespace Microsoft.Qwiq { public interface ITeamFoundationIdentity { + /// + /// Gets the unique identifier for the identity's provider. + /// + /// The descriptor. IIdentityDescriptor Descriptor { get; } + + /// + /// Gets the full name of the identity for display purposes. The display name can come from the identity provider, or + /// may have been set as a custom display name within TFS. + /// + /// The display name. string DisplayName { get; } + + /// + /// Gets a value indicating whether the identity is current with the provider. + /// + /// true if this instance is current; otherwise, false. bool IsActive { get; } + + /// + /// Indicates that the identity is a group, possibly containing other identities as members. + /// bool IsContainer { get; } + + /// + /// Gets the set of of groups containing this identity. + /// + /// The member of. IEnumerable MemberOf { get; } + + /// + /// Gets the set of s for members of this identity. + /// + /// The members. IEnumerable Members { get; } + + /// + /// Gets the team foundation identifier. + /// + /// The team foundation identifier. Guid TeamFoundationId { get; } + + /// + /// Gets the unique name of this identity. + /// + /// + /// The unique name is a combination of the domain and account name properties of the identity and the + /// . + /// If the current user is active: and there is no domain, then the unique name equals the account name; otherwise, the + /// unique name equals DOMAIN\ACCOUNTNAME. + /// If the current user is not active and there is no domain, then the unique name equals the account name and the + /// ; otherwise, the unique name equals DOMAIN\ACCOUNTNAME:UniqueUserId + /// + /// + /// DanJ + /// DanJ:1 + /// CONTOSO\DanJ + /// CONTOSO\DanJ:1 + /// + /// The unique name of the identity. string UniqueName { get; } + + /// + /// Gets the unique user identifier used to distinguish deleted accounts from one another. + /// + /// + /// If the current user is active (e.g. not deleted), the value is equal to + /// . + /// + /// The unique user identifier. int UniqueUserId { get; } - } -} + /// + /// Attribute accessor. Will return the caller supplied default value if attribute + /// is not present (will not throw). + /// + string GetAttribute(string name, string defaultValue); + + /// Property accessor. Will throw if not found. + object GetProperty(string name); + + /// + /// Property bag. This could be useful, for example if consumer has + /// to iterate through current properties and modify / remove + /// some based on pattern matching property names. + /// + IEnumerable> GetProperties(); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index a3216818..2ffb19ff 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -1,25 +1,29 @@ -using System; - using Microsoft.Qwiq.Credentials; +using System; namespace Microsoft.Qwiq { public interface ITfsTeamProjectCollection { + /// Gets the credentials for this project collection. TfsCredentials AuthorizedCredentials { get; } + /// + /// The identity who the calls to the server are being made for. + /// ITeamFoundationIdentity AuthorizedIdentity { get; } ICommonStructureService CommonStructureService { get; } + /// Returns true if this object has successfully authenticated. bool HasAuthenticated { get; } IIdentityManagementService IdentityManagementService { get; } - Uri Uri { get; } - + /// This is used to convert dates and times to UTC. TimeZone TimeZone { get; } - } - + /// The base url for this connection + Uri Uri { get; } + } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 80812ac3..fe7be6e5 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -7,20 +7,16 @@ namespace Microsoft.Qwiq /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference /// all the TFS libraries. /// - public interface IWorkItem : IWorkItemCommon + public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable { new int AttachedFileCount { get; } - IEnumerable Attachments { get; } - new DateTime ChangedDate { get; } new DateTime CreatedDate { get; } new int ExternalLinkCount { get; } - IFieldCollection Fields { get; } - new int HyperLinkCount { get; } new int RelatedLinkCount { get; } @@ -36,11 +32,6 @@ public interface IWorkItem : IWorkItemCommon string Keywords { get; set; } - /// - /// Gets the links of the work item in this revision. - /// - ICollection Links { get; } - new int Rev { get; } /// diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 73f13809..858ffaab 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -6,35 +6,101 @@ namespace Microsoft.Qwiq { /// - /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference - /// all the TFS libraries. + /// Represents the work item store resource. /// + /// public interface IWorkItemStore : IDisposable { + /// + /// Indicates the communication type used for the work item store. + /// + ClientType ClientType { get; } + + /// Gets the credentials for this project collection. + /// The authorized credentials. TfsCredentials AuthorizedCredentials { get; } - IEnumerable Projects { get; } + /// + /// Returns the collection of all known s associated with this instance. + /// + /// The field definitions. + IFieldDefinitionCollection FieldDefinitions { get; } + + /// + /// Gets a collection of associated with this instance. + /// + /// The projects. + IProjectCollection Projects { get; } + /// + /// Gets the team project collection. + /// + /// The team project collection. ITfsTeamProjectCollection TeamProjectCollection { get; } + /// + /// Gets the time zone. + /// + /// The time zone. TimeZone TimeZone { get; } - string UserDisplayName { get; } + /// + /// Gets the name of the user account. + /// + /// The name of the user account. + string UserAccountName { get; } - string UserIdentityName { get; } + /// + /// Gets the display name of the user. + /// + /// The display name of the user. + string UserDisplayName { get; } + /// + /// Gets the user sid. + /// + /// The user sid. string UserSid { get; } + /// + /// Gets the work item link types associated with this instance. + /// + /// The work item link types. WorkItemLinkTypeCollection WorkItemLinkTypes { get; } + /// + /// Runs a query using the WIQL string passed in and returns a collection of . + /// + /// The WIQL query string. + /// + /// True to ignore time values so that DateTime objects are treated as dates; otherwise, + /// false. + /// + /// IEnumerable<IWorkItem>. IEnumerable Query(string wiql, bool dayPrecision = false); + /// + /// Queries the specified work item IDs. + /// + /// A collection of work item IDs. + /// Optional: The date of the desired work item state. + /// IEnumerable<IWorkItem>. IEnumerable Query(IEnumerable ids, DateTime? asOf = null); + /// + /// Queries the specified work item ID. + /// + /// The work item ID. + /// Optional: The desired date of the work item state. + /// IWorkItem Query(int id, DateTime? asOf = null); + /// + /// Queries the links. + /// + /// The WIQL query. + /// if set to true [day precision]. + /// IEnumerable<IWorkItemLinkInfo>. IEnumerable QueryLinks(string wiql, bool dayPrecision = false); - - IFieldDefinitionCollection FieldDefinitions { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemTypeCollection.cs b/src/Qwiq.Core/IWorkItemTypeCollection.cs new file mode 100644 index 00000000..05f409b1 --- /dev/null +++ b/src/Qwiq.Core/IWorkItemTypeCollection.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Microsoft.Qwiq +{ + public interface IWorkItemTypeCollection : IEnumerable + { + IWorkItemType this[string typeName] { get; } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/IdentityDescriptorComparer.cs b/src/Qwiq.Core/IdentityDescriptorComparer.cs new file mode 100644 index 00000000..26100559 --- /dev/null +++ b/src/Qwiq.Core/IdentityDescriptorComparer.cs @@ -0,0 +1,60 @@ +using System; + +namespace Microsoft.Qwiq +{ + public class IdentityDescriptorComparer : GenericComparer + { + public static IdentityDescriptorComparer Instance => Nested.Instance; + + public override bool Equals(IIdentityDescriptor x, IIdentityDescriptor y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return string.Equals(x.Identifier, y.Identifier, StringComparison.OrdinalIgnoreCase) + && string.Equals(x.IdentityType, y.IdentityType, StringComparison.OrdinalIgnoreCase); + } + + private static readonly System.Security.Cryptography.MD5CryptoServiceProvider Md5Provider = new System.Security.Cryptography.MD5CryptoServiceProvider(); + // the database is usually set to Latin1_General_CI_AS which is codepage 1252 + private static readonly System.Text.Encoding Encoding = System.Text.Encoding.GetEncoding(1252); + + private static int ComputeStringHash(string sourceString, int modulo = 0) + { + var md5Bytes = Md5Provider.ComputeHash(Encoding.GetBytes(sourceString)); + var result = BitConverter.ToInt32(new[] { md5Bytes[15], md5Bytes[14], md5Bytes[13], md5Bytes[12] }, 0); + return modulo == 0 + ? result + : Math.Abs(result) % modulo; + } + + public override int GetHashCode(IIdentityDescriptor obj) + { + unchecked + { + var hash = 27; + + hash = (13 * hash) ^ ComputeStringHash(obj.Identifier); + hash = (13 * hash) ^ ComputeStringHash(obj.IdentityType); + + return hash; + } + } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly IdentityDescriptorComparer Instance = new IdentityDescriptorComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Project.cs b/src/Qwiq.Core/Project.cs index 4cc45afc..c248de60 100644 --- a/src/Qwiq.Core/Project.cs +++ b/src/Qwiq.Core/Project.cs @@ -9,18 +9,16 @@ public class Project : IProject, IComparer, IEquatable private readonly Lazy> _iteration; - private readonly Lazy> _wits; + private readonly Lazy _wits; internal Project( - int id, Guid guid, string name, Uri uri, - Lazy> wits, + Lazy wits, Lazy> area, Lazy> iteration) { - Id = id; Guid = guid; Name = name ?? throw new ArgumentNullException(nameof(name)); Uri = uri ?? throw new ArgumentNullException(nameof(uri)); @@ -47,7 +45,7 @@ public bool Equals(IProject other) public Guid Guid { get; } - public int Id { get; } + public IEnumerable IterationRootNodes => _iteration.Value; @@ -55,7 +53,7 @@ public bool Equals(IProject other) public Uri Uri { get; } - public IEnumerable WorkItemTypes => _wits.Value; + public IWorkItemTypeCollection WorkItemTypes => _wits.Value; public override bool Equals(object obj) { diff --git a/src/Qwiq.Core/ProjectCollection.cs b/src/Qwiq.Core/ProjectCollection.cs new file mode 100644 index 00000000..7bafff3f --- /dev/null +++ b/src/Qwiq.Core/ProjectCollection.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq +{ + internal class ProjectCollection : IProjectCollection + { + private readonly Dictionary _guidMap; + + private readonly Dictionary _nameMap; + + private readonly IList _projects; + + internal ProjectCollection(params IProject[] projects) + : this(projects as IEnumerable) + { + } + + internal ProjectCollection(IEnumerable projects) + { + _projects = projects?.ToList() ?? throw new ArgumentNullException(nameof(projects)); + + _nameMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _guidMap = new Dictionary(); + + for (var i = 0; i < _projects.Count; i++) + { + _nameMap.Add(_projects[i].Name, i); + _guidMap.Add(_projects[i].Guid, i); + } + } + + public IProject this[string projectName] + { + get + { + if (projectName == null) throw new ArgumentNullException(nameof(projectName)); + if (_nameMap.TryGetValue(projectName, out int idx)) return _projects[idx]; + + throw new Exception(); + } + } + + public IProject this[Guid projectGuid] + { + get + { + if (projectGuid == Guid.Empty) throw new ArgumentException(); + + if (_guidMap.TryGetValue(projectGuid, out int idx)) return _projects[idx]; + + throw new Exception(); + } + } + + public IEnumerator GetEnumerator() + { + return _projects.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public static implicit operator ProjectCollection(Project project) + { + return new ProjectCollection(project); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 655d6415..9b8d22bf 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -100,7 +100,7 @@ - + @@ -113,6 +113,7 @@ + @@ -120,6 +121,7 @@ + @@ -136,6 +138,7 @@ + @@ -158,18 +161,23 @@ + + + + + @@ -182,6 +190,7 @@ + diff --git a/src/Qwiq.Core/TeamFoundationIdentity.cs b/src/Qwiq.Core/TeamFoundationIdentity.cs new file mode 100644 index 00000000..c682df2e --- /dev/null +++ b/src/Qwiq.Core/TeamFoundationIdentity.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +using Microsoft.VisualStudio.Services.Common; + +namespace Microsoft.Qwiq +{ + public abstract class TeamFoundationIdentity : ITeamFoundationIdentity, IEquatable + { + private string _uniqueName; + + public abstract IIdentityDescriptor Descriptor { get; internal set; } + + public abstract string DisplayName { get; } + + public virtual bool IsActive { get; } + + public virtual bool IsContainer + { + get + { + var schema = GetAttribute(IdentityAttributeTags.SchemaClassName, null); + if (!string.IsNullOrEmpty(schema) && string.Equals( + schema, + IdentityConstants.SchemaClassGroup, + StringComparison.OrdinalIgnoreCase)) return true; + + return false; + } + } + + public abstract IEnumerable MemberOf { get; } + + public abstract IEnumerable Members { get; } + + public virtual Guid TeamFoundationId { get; internal set; } + + public virtual string UniqueName + { + get + { + if (!string.IsNullOrEmpty(_uniqueName)) return _uniqueName; + + var domain = GetAttribute("Domain", string.Empty); + var account = GetAttribute("Account", string.Empty); + + if (UniqueUserId == IdentityConstants.ActiveUniqueId) _uniqueName = string.IsNullOrEmpty(domain) ? account : $"{domain}\\{account}"; + else + _uniqueName = string.IsNullOrEmpty(domain) + ? $"{account}:{UniqueUserId}" + : $"{domain}\\{account}:{UniqueUserId}"; + + return _uniqueName; + } + } + + public virtual int UniqueUserId { get; } + + public bool Equals(ITeamFoundationIdentity other) + { + return TeamFoundationIdentityComparer.Instance.Equals(this, other); + } + public override bool Equals(object obj) + { + return Equals(obj as ITeamFoundationIdentity); + } + + public abstract string GetAttribute(string name, string defaultValue); + + public override int GetHashCode() + { + return TeamFoundationIdentityComparer.Instance.GetHashCode(this); + } + + public abstract IEnumerable> GetProperties(); + + public abstract object GetProperty(string name); + public override string ToString() + { + return UniqueName; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/TeamFoundationIdentityComparer.cs b/src/Qwiq.Core/TeamFoundationIdentityComparer.cs new file mode 100644 index 00000000..e22e45b8 --- /dev/null +++ b/src/Qwiq.Core/TeamFoundationIdentityComparer.cs @@ -0,0 +1,44 @@ +using System; + +namespace Microsoft.Qwiq +{ + public class TeamFoundationIdentityComparer : GenericComparer + { + public static TeamFoundationIdentityComparer Instance => Nested.Instance; + + public override int GetHashCode(ITeamFoundationIdentity obj) + { + unchecked + { + var hash = 27; + hash = (13 * hash) ^ obj.UniqueName.GetHashCode(); + hash = (13 * hash) ^ obj.TeamFoundationId.GetHashCode(); + return hash; + } + } + + public override bool Equals(ITeamFoundationIdentity x, ITeamFoundationIdentity y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return (string.Equals(x.UniqueName, y.UniqueName, StringComparison.OrdinalIgnoreCase)); + } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly TeamFoundationIdentityComparer Instance = new TeamFoundationIdentityComparer(); + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index 07fc536b..4a9b2b22 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -4,28 +4,45 @@ namespace Microsoft.Qwiq { /// - /// A compatability class + /// A compatability class /// /// /// - public abstract class WorkItem : WorkItemCommon, IWorkItem + public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable { private readonly IWorkItemType _type; - protected WorkItem(IWorkItemType type) + private bool _useFields = true; + + private readonly Lazy _fields; + + protected internal WorkItem(IDictionary fields) + : base(fields) + { + } + + protected internal WorkItem(IWorkItemType type) { _type = type ?? throw new ArgumentNullException(nameof(type)); + _fields = new Lazy(()=> new FieldCollection(this, Type.FieldDefinitions, (revision, definition) => new Field(revision, definition))); } - protected WorkItem() + protected internal WorkItem(IWorkItemType type, Func fieldCollectionFactory) { + _type = type ?? throw new ArgumentNullException(nameof(type)); + _fields = new Lazy(fieldCollectionFactory); } public virtual int Revision => Rev; + public bool Equals(IWorkItem other) + { + return WorkItemComparer.Instance.Equals(this, other); + } + public new virtual int AttachedFileCount => base.AttachedFileCount.GetValueOrDefault(0); - public virtual IEnumerable Attachments => throw new NotImplementedException(); + public virtual IEnumerable Attachments => throw new NotSupportedException(); public new virtual DateTime ChangedDate => base.ChangedDate.GetValueOrDefault(DateTime.MinValue); @@ -33,13 +50,16 @@ protected WorkItem() public new virtual int ExternalLinkCount => base.ExternalLinkCount.GetValueOrDefault(0); - public virtual IFieldCollection Fields => throw new NotImplementedException(); + public virtual IFieldCollection Fields => _fields == null ? throw new NotSupportedException() : _fields.Value; + + public int Index => -2; + public new virtual int HyperLinkCount => base.HyperLinkCount.GetValueOrDefault(0); public new virtual int Id => base.Id.GetValueOrDefault(0); - public virtual bool IsDirty => throw new NotImplementedException(); + public virtual bool IsDirty => throw new NotSupportedException(); public virtual string Keywords { @@ -47,7 +67,7 @@ public virtual string Keywords set => throw new NotSupportedException(); } - public virtual ICollection Links => throw new NotImplementedException(); + public virtual ICollection Links => throw new NotSupportedException(); public new virtual int RelatedLinkCount => base.RelatedLinkCount.GetValueOrDefault(0); @@ -55,70 +75,118 @@ public virtual string Keywords public new virtual DateTime RevisedDate => base.RevisedDate.GetValueOrDefault(DateTime.MinValue); - public virtual IEnumerable Revisions => throw new NotImplementedException(); + public virtual IEnumerable Revisions => throw new NotSupportedException(); - public virtual IWorkItemType Type => _type ?? throw new NotImplementedException(); + public virtual IWorkItemType Type => _type ?? throw new NotSupportedException(); public abstract Uri Uri { get; } + + public override object this[string name] + { + get + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (_useFields) + try + { + return Fields[name].Value; + } + catch (NotSupportedException) + { + _useFields = false; + } + + return GetValue(name); + } + set + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (_useFields) + try + { + Fields[name].Value = value; + } + catch (NotSupportedException) + { + _useFields = false; + } + SetValue(name, value); + } + } - public virtual void ApplyRules(bool doNotUpdateChangedBy = false) + public string GetTagLine() { throw new NotImplementedException(); } + public virtual void ApplyRules(bool doNotUpdateChangedBy = false) + { + throw new NotSupportedException(); + } + public virtual void Close() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual IWorkItem Copy() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual IHyperlink CreateHyperlink(string location) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual bool IsValid() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual void Open() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual void PartialOpen() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual void Reset() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual void Save() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual void Save(SaveFlags saveFlags) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public virtual IEnumerable Validate() { - throw new NotImplementedException(); + throw new NotSupportedException(); + } + + public override bool Equals(object obj) + { + return WorkItemComparer.Instance.Equals(this, obj as IWorkItem); + } + + public override int GetHashCode() + { + return WorkItemComparer.Instance.GetHashCode(this); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index 6079ee17..62d54410 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -1,9 +1,20 @@ using System; +using System.Collections.Generic; namespace Microsoft.Qwiq { - public abstract class WorkItemCommon : WorkItemCore, IWorkItemCommon + public abstract class WorkItemCommon : WorkItemCore, IWorkItemCommon, IEquatable { + protected internal WorkItemCommon() + :base() + { + } + + protected internal WorkItemCommon(IDictionary fields) + :base(fields) + { + } + public virtual int? AreaId { get => GetValue(CoreFieldRefNames.AreaId); @@ -85,5 +96,12 @@ public virtual string Title public virtual int? Watermark => GetValue(CoreFieldRefNames.Watermark); public virtual string WorkItemType => GetValue(CoreFieldRefNames.WorkItemType); + + + + public bool Equals(IWorkItemCommon other) + { + return NullableIdentifiableComparer.Instance.Equals(this, other); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemComparer.cs b/src/Qwiq.Core/WorkItemComparer.cs new file mode 100644 index 00000000..a068d852 --- /dev/null +++ b/src/Qwiq.Core/WorkItemComparer.cs @@ -0,0 +1,120 @@ +namespace Microsoft.Qwiq +{ + public class WorkItemComparer : GenericComparer + { + private WorkItemComparer() + { + } + + public static WorkItemComparer Instance => Nested.Instance; + + public override bool Equals(IWorkItem x, IWorkItem y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return IdentifiableComparer.Instance.Equals(x, y); + } + + public override int GetHashCode(IWorkItem obj) + { + return IdentifiableComparer.Instance.GetHashCode(obj); + } + + private class Nested + { + internal static readonly WorkItemComparer Instance = new WorkItemComparer(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } + + public class IdentifiableComparer : GenericComparer> + { + public static IdentifiableComparer Instance => Nested.Instance; + + private IdentifiableComparer() + { + + } + + public override bool Equals(IIdentifiable x, IIdentifiable y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return x.Id == y.Id; + } + + public override int GetHashCode(IIdentifiable obj) + { + unchecked + { + var hash = 27; + + hash = (13 * hash) ^ obj.Id.GetHashCode(); + + return hash; + } + } + + private class Nested + { + internal static readonly IdentifiableComparer Instance = new IdentifiableComparer(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } + + public class NullableIdentifiableComparer : GenericComparer> + { + public static NullableIdentifiableComparer Instance => Nested.Instance; + + private NullableIdentifiableComparer() + { + + } + + public override bool Equals(IIdentifiable x, IIdentifiable y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return x.Id == y.Id; + } + + public override int GetHashCode(IIdentifiable obj) + { + unchecked + { + var hash = 27; + + hash = (13 * hash) ^ (obj.Id.HasValue ? obj.Id.GetHashCode() : 0); + + return hash; + } + } + + private class Nested + { + internal static readonly NullableIdentifiableComparer Instance = new NullableIdentifiableComparer(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index 9ea208b8..e363fd3a 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -1,9 +1,22 @@ using System; +using System.Collections.Generic; namespace Microsoft.Qwiq { - public abstract class WorkItemCore : IWorkItemCore + public abstract class WorkItemCore : IWorkItemCore, IEquatable, IRevisionInternal { + private readonly IDictionary _fields; + + protected internal WorkItemCore() + :this(null) + { + } + + protected internal WorkItemCore(IDictionary fields) + { + _fields = fields ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + } + public virtual int? Id => GetValue(CoreFieldRefNames.Id); public virtual int? Rev => GetValue(CoreFieldRefNames.Rev); @@ -21,7 +34,7 @@ public abstract class WorkItemCore : IWorkItemCore /// /// name is null /// - public object this[string name] + public virtual object this[string name] { get @@ -41,8 +54,46 @@ protected virtual T GetValue(string name) return TypeParser.Default.Parse(GetValue(name)); } - protected abstract object GetValue(string name); + protected virtual object GetValue(string name) + { + return !_fields.TryGetValue(name, out object val) ? null : val; + } + + protected virtual void SetValue(string name, object value) + { + _fields[name] = value; + } + + public bool Equals(IWorkItemCore other) + { + return NullableIdentifiableComparer.Instance.Equals(this, other); + } + + public override bool Equals(object obj) + { + return NullableIdentifiableComparer.Instance.Equals(this, obj as IWorkItemCore); + } + + public override int GetHashCode() + { + return NullableIdentifiableComparer.Instance.GetHashCode(this); + } + + public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) + { + return GetValue(fieldDefinition.ReferenceName); + } + + public void SetFieldValue(IFieldDefinition fieldDefinition, object value) + { + SetValue(fieldDefinition.ReferenceName, value); + } + } + + internal interface IRevisionInternal + { + object GetCurrentFieldValue(IFieldDefinition fieldDefinition); - protected abstract void SetValue(string name, object value); + void SetFieldValue(IFieldDefinition fieldDefinition, object value); } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs index 399d4529..9aa64cf8 100644 --- a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs @@ -10,9 +10,7 @@ public override bool Equals(IWorkItemLinkInfo x, IWorkItemLinkInfo y) if (ReferenceEquals(x, null)) return false; if (ReferenceEquals(y, null)) return false; - return x.IsLocked == y.IsLocked - && x.LinkTypeId == y.LinkTypeId - && x.SourceId == y.SourceId + return x.SourceId == y.SourceId && x.TargetId == y.TargetId; } @@ -21,8 +19,6 @@ public override int GetHashCode(IWorkItemLinkInfo obj) unchecked { var hash = 27; - hash = (13 * hash) ^ obj.IsLocked.GetHashCode(); - hash = (13 * hash) ^ obj.LinkTypeId.GetHashCode(); hash = (13 * hash) ^ obj.SourceId.GetHashCode(); hash = (13 * hash) ^ obj.TargetId.GetHashCode(); diff --git a/src/Qwiq.Core/WorkItemTypeCollection.cs b/src/Qwiq.Core/WorkItemTypeCollection.cs new file mode 100644 index 00000000..74f39125 --- /dev/null +++ b/src/Qwiq.Core/WorkItemTypeCollection.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq +{ + internal class WorkItemTypeCollection : IWorkItemTypeCollection + { + private readonly IDictionary _nameMap; + + private readonly IList _workItemTypes; + + public WorkItemTypeCollection(params IWorkItemType[] workItemTypes) + : this(workItemTypes as IEnumerable) + { + } + + public WorkItemTypeCollection(IEnumerable workItemTypes) + { + _workItemTypes = workItemTypes?.ToList() ?? throw new ArgumentNullException(nameof(workItemTypes)); + _nameMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < _workItemTypes.Count; i++) _nameMap.Add(_workItemTypes[i].Name, i); + } + + public IWorkItemType this[string typeName] + { + get + { + if (typeName == null) throw new ArgumentNullException(nameof(typeName)); + if (_nameMap.TryGetValue(typeName, out int idx)) return _workItemTypes[idx]; + + throw new Exception(); + } + } + + public IEnumerator GetEnumerator() + { + return _workItemTypes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity/IdentityManagementService.Extensions.cs b/src/Qwiq.Identity/IdentityManagementService.Extensions.cs index ef8332c0..d1922d28 100644 --- a/src/Qwiq.Identity/IdentityManagementService.Extensions.cs +++ b/src/Qwiq.Identity/IdentityManagementService.Extensions.cs @@ -2,12 +2,13 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Identity { public static class IdentityManagementServiceExtensions { private const string TfsLoggedInIdentityType = "Microsoft.IdentityModel.Claims.ClaimsIdentity"; - private const string TfsBindPendingIdentityType = "Microsoft.TeamFoundation.BindPendingIdentity"; public static string[] GetAliasesForDisplayName(this IIdentityManagementService ims, string displayName) { @@ -24,7 +25,7 @@ public static IDictionary GetAliasesForDisplayNames(this IIden kvp => kvp.Key, kvp => kvp.Value - .Where(identity => identity != null && !identity.IsContainer && identity.IsActive) + .Where(identity => identity != null && !identity.IsContainer && identity.UniqueUserId == IdentityConstants.ActiveUniqueId) .Select(i => i.GetUserAlias()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray()); @@ -95,8 +96,8 @@ private static IDictionary> CreatePossi { var loggedInAccountString = $"{tenantId}\\{alias}@{domain}"; - descriptorsForAlias.Add(ims.CreateIdentityDescriptor(TfsLoggedInIdentityType, loggedInAccountString)); - descriptorsForAlias.Add(ims.CreateIdentityDescriptor(TfsBindPendingIdentityType, + descriptorsForAlias.Add(ims.CreateIdentityDescriptor(IdentityConstants.ClaimsType, loggedInAccountString)); + descriptorsForAlias.Add(ims.CreateIdentityDescriptor(IdentityConstants.BindPendingIdentityType, "upn:" + loggedInAccountString)); } diff --git a/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs index 88a57765..469cfcd3 100644 --- a/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs @@ -23,21 +23,14 @@ public BulkIdentityAwareAttributeMapperStrategy(IPropertyInspector inspector, II public override void Map(Type targeWorkItemType, IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) { - var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, new WorkItemKeyComparer()); - - if (!workingSet.Any()) - { - return; - } + var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Instance); + if (!workingSet.Any()) return; var validIdentityProperties = GetWorkItemIdentityFieldNameToIdentityPropertyMap(targeWorkItemType, _inspector); - - if (!validIdentityProperties.Any()) - { - return; - } + if (!validIdentityProperties.Any())return; + var accessor = TypeAccessor.Create(targeWorkItemType, true); - var validIdentityFieldsWithWorkItems = GetWorkItemsWithIdentityFieldValues(workingSet.Keys.ToList(), validIdentityProperties.Keys.ToList()); + var validIdentityFieldsWithWorkItems = GetWorkItemsWithIdentityFieldValues(workingSet.Keys, validIdentityProperties.Keys.ToList()); var identitySearchTerms = GetIdentitySearchTerms(validIdentityFieldsWithWorkItems).ToList(); var identitySearchResults = GetIdentityMap(_identityManagementService, identitySearchTerms); @@ -61,7 +54,7 @@ internal static IDictionary GetIdentityMap(IIdentityManagementSe return ims.GetAliasesForDisplayNames(searchTerms.ToArray()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value.FirstOrDefault()); } - internal static ICollection GetWorkItemsWithIdentityFieldValues(IReadOnlyCollection workItems, IReadOnlyCollection witFieldNames) + internal static ICollection GetWorkItemsWithIdentityFieldValues(IEnumerable workItems, IReadOnlyCollection witFieldNames) { return workItems.Select( diff --git a/src/Qwiq.Identity/Mapper/IdentifiableComparer.cs b/src/Qwiq.Identity/Mapper/IdentifiableComparer.cs new file mode 100644 index 00000000..870fb17f --- /dev/null +++ b/src/Qwiq.Identity/Mapper/IdentifiableComparer.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +using Microsoft.Qwiq.Mapper; + +namespace Microsoft.Qwiq.Identity.Mapper +{ + + + internal class IdentifiableComparer : GenericComparer + { + private IdentifiableComparer() + { + + } + + public static IdentifiableComparer Instance => Nested.Instance; + + public override bool Equals(IIdentifiable x, IIdentifiable y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return x.Id == y.Id; + } + + public override int GetHashCode(IIdentifiable obj) + { + unchecked + { + + + var hash = 27; + + hash = (13 * hash) ^ obj.Id.GetHashCode(); + + return hash; + } + } + + private class Nested + { + internal static readonly IdentifiableComparer Instance = new IdentifiableComparer(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} diff --git a/src/Qwiq.Identity/Mapper/WorkItemKeyComparer.cs b/src/Qwiq.Identity/Mapper/WorkItemKeyComparer.cs deleted file mode 100644 index 302e2021..00000000 --- a/src/Qwiq.Identity/Mapper/WorkItemKeyComparer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.Qwiq.Identity.Mapper -{ - internal class WorkItemKeyComparer : IEqualityComparer - { - public bool Equals(IWorkItem x, IWorkItem y) - { - return x.Id == y.Id; - } - - public int GetHashCode(IWorkItem obj) - { - return obj.Id; - } - } -} diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 95cd2e3c..927d5f81 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -46,8 +46,8 @@ + - diff --git a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs b/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs deleted file mode 100644 index 03e7488f..00000000 --- a/test/Qwiq.Core.Tests/IdentityDescriptorProxyTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.Qwiq.Tests.Common; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Should; - - - -namespace Microsoft.Qwiq.Core.Tests -{ - public abstract class IdentityDescriptorProxyTests : ContextSpecification - { - protected Soap.IdentityDescriptor IdentityDescriptorProxy; - protected TeamFoundation.Framework.Client.IdentityDescriptor IdentityDescriptor; - - public override void When() - { - IdentityDescriptorProxy = new Soap.IdentityDescriptor(IdentityDescriptor); - } - } - - [TestClass] - public class given_an_IdentityDescriptor_when_a_proxy_is_created : IdentityDescriptorProxyTests - { - private const string Identifier = "identifier"; - private const string IdentityType = "identityType"; - - public override void Given() - { - IdentityDescriptor = new TeamFoundation.Framework.Client.IdentityDescriptor(IdentityType, Identifier); - } - - [TestMethod] - public void the_identifier_can_be_retrieved() - { - IdentityDescriptorProxy.Identifier.ShouldEqual(Identifier); - } - - [TestMethod] - public void the_identity_type_can_be_retrieve() - { - IdentityDescriptorProxy.IdentityType.ShouldEqual(IdentityType); - } - } -} - diff --git a/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs b/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs index 8c5d1ed4..e4dfa05b 100644 --- a/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs +++ b/test/Qwiq.Core.Tests/IntegrationContextSpecification.cs @@ -13,7 +13,6 @@ public abstract class IntegrationContextSpecificationSpecification : WorkItemSto protected Result RestResult { get; set; } protected Result SoapResult { get; set; } - [TestMethod] [TestCategory("localOnly")] [TestCategory("SOAP")] @@ -48,6 +47,8 @@ public override void Cleanup() { SoapResult?.Dispose(); RestResult?.Dispose(); + + base.Cleanup(); } [TestMethod] @@ -59,7 +60,7 @@ public void CoreFields_are_equal() { try { - RestResult.WorkItem[field].ShouldEqual(SoapResult.WorkItem[field], field); + RestResult.WorkItem[field]?.ToString().ShouldEqual(SoapResult.WorkItem[field]?.ToString(), field); } catch (Exception e) { @@ -69,7 +70,7 @@ public void CoreFields_are_equal() if (exceptions.Any()) { - throw new AggregateException(ShouldExtensions.EachToUsefulString(exceptions), exceptions); + throw new AggregateException(exceptions.EachToUsefulString(), exceptions); } } diff --git a/test/Qwiq.Core.Tests/LinkIntegrationTests.cs b/test/Qwiq.Core.Tests/LinkIntegrationTests.cs index b6a30e05..34e4c686 100644 --- a/test/Qwiq.Core.Tests/LinkIntegrationTests.cs +++ b/test/Qwiq.Core.Tests/LinkIntegrationTests.cs @@ -1,46 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +//using Microsoft.VisualStudio.TestTools.UnitTesting; -using Should; +//using Should; -namespace Microsoft.Qwiq.Core.Tests -{ - public abstract class LinkIntegrationTests : WorkItemStoreComparisonContextSpecification - { - protected IWorkItem RestResult { get; private set; } - protected IWorkItem SoapResult { get; private set; } +//namespace Microsoft.Qwiq.Core.Tests +//{ +// public abstract class LinkIntegrationTests : WorkItemStoreComparisonContextSpecification +// { +// protected IWorkItem RestResult { get; private set; } +// protected IWorkItem SoapResult { get; private set; } - protected int Id { get; set; } +// protected int Id { get; set; } - public override void When() - { - RestResult = Rest.Query(Id); - SoapResult = Soap.Query(Id); - } - } +// public override void When() +// { +// RestResult = Rest.Query(Id); +// SoapResult = Soap.Query(Id); +// } +// } - [TestClass] - public class Given_a_workitem_with_links : LinkIntegrationTests - { - public override void Given() - { - base.Given(); - Id = 156027; - } +// [TestClass] +// public class Given_a_workitem_with_links : LinkIntegrationTests +// { +// public override void Given() +// { +// base.Given(); +// Id = 156027; +// } - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - [TestCategory("REST")] - [ExpectedException(typeof(NotImplementedException), "This is not yet implemented in the REST client.")] - public void The_Links_are_equal() - { - RestResult.Links.ShouldContainOnly(SoapResult.Links); - } - } -} +// [TestMethod] +// [TestCategory("localOnly")] +// [TestCategory("SOAP")] +// [TestCategory("REST")] +// [ExpectedException(typeof(NotImplementedException), "This is not yet implemented in the REST client.")] +// public void The_Links_are_equal() +// { +// RestResult.Links.ShouldContainOnly(SoapResult.Links); +// } +// } +//} diff --git a/test/Qwiq.Core.Tests/Mocks/MockIdentityManagementService2.cs b/test/Qwiq.Core.Tests/Mocks/MockIdentityManagementService2.cs index 0fe3b27d..8ec15384 100644 --- a/test/Qwiq.Core.Tests/Mocks/MockIdentityManagementService2.cs +++ b/test/Qwiq.Core.Tests/Mocks/MockIdentityManagementService2.cs @@ -7,211 +7,221 @@ namespace Microsoft.Qwiq.Core.Tests.Mocks { public class MockIdentityManagementService2 : IIdentityManagementService2 { - private TeamFoundationIdentity[] GetNullIdentities() + private TeamFoundation.Framework.Client.TeamFoundationIdentity[] GetNullIdentities() { - return new TeamFoundationIdentity[] + return new TeamFoundation.Framework.Client.TeamFoundationIdentity[] { null }; } - public TeamFoundationIdentity[] ReadIdentities(IdentityDescriptor[] descriptors, MembershipQuery queryMembership, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] ReadIdentities(IdentityDescriptor[] descriptors, MembershipQuery queryMembership, ReadIdentityOptions readOptions) { return GetNullIdentities(); } - public TeamFoundationIdentity ReadIdentity(IdentityDescriptor descriptor, MembershipQuery queryMembership, + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(IdentityDescriptor descriptor, MembershipQuery queryMembership, ReadIdentityOptions readOptions) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] ReadIdentities(Guid[] teamFoundationIds, MembershipQuery queryMembership) + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] ReadIdentities(Guid[] teamFoundationIds, MembershipQuery queryMembership) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[][] ReadIdentities(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string[] searchFactorValues, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[][] ReadIdentities(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string[] searchFactorValues, MembershipQuery queryMembership, ReadIdentityOptions readOptions) { return new []{ GetNullIdentities() }; } - public TeamFoundationIdentity ReadIdentity(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string searchFactorValue, + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string searchFactorValue, MembershipQuery queryMembership, ReadIdentityOptions readOptions) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[][] ReadIdentities(IdentitySearchFactor searchFactor, string[] searchFactorValues, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[][] ReadIdentities(IdentitySearchFactor searchFactor, string[] searchFactorValues, MembershipQuery queryMembership, ReadIdentityOptions readOptions) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue, + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue, MembershipQuery queryMembership, ReadIdentityOptions readOptions) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public IdentityDescriptor CreateApplicationGroup(string scopeId, string groupName, string groupDescription) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void DeleteApplicationGroup(IdentityDescriptor groupDescriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void AddMemberToApplicationGroup(IdentityDescriptor groupDescriptor, IdentityDescriptor descriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RemoveMemberFromApplicationGroup(IdentityDescriptor groupDescriptor, IdentityDescriptor descriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public bool IsMember(IdentityDescriptor groupDescriptor, IdentityDescriptor descriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public bool RefreshIdentity(IdentityDescriptor descriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public string GetScopeName(string scopeId) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public bool IsOwner(IdentityDescriptor descriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public bool IsOwnedWellKnownGroup(IdentityDescriptor descriptor) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public string IdentityDomainScope { - get { throw new NotImplementedException(); } + get { throw new NotSupportedException(); } } public void UpdateApplicationGroup(IdentityDescriptor groupDescriptor, GroupProperty groupProperty, string newValue) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] ListApplicationGroups(string scopeId, ReadIdentityOptions readOptions) + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] ListApplicationGroups(string scopeId, ReadIdentityOptions readOptions) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] GetMostRecentlyUsedUsers() + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] GetMostRecentlyUsedUsers() { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] GetMostRecentlyUsedUsersEx(Guid teamId) + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] GetMostRecentlyUsedUsersEx(Guid teamId) { - throw new NotImplementedException(); + throw new NotSupportedException(); + } + + public void AddRecentUser(TeamFoundation.Framework.Client.TeamFoundationIdentity identity) + { + throw new NotSupportedException(); } public void AddRecentUser(TeamFoundationIdentity identity) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void AddRecentUser(Guid teamFoundationId) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity ReadIdentity(string generalSearchValue) + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(string generalSearchValue) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public FilteredIdentitiesList ReadFilteredIdentities(string expression, int suggestedPageSize, string lastSearchResult, bool lookForward, int queryMembership) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void SetCustomDisplayName(string customDisplayName) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void ClearCustomDisplayName() { - throw new NotImplementedException(); + throw new NotSupportedException(); + } + + public void UpdateExtendedProperties(TeamFoundation.Framework.Client.TeamFoundationIdentity identity) + { + throw new NotSupportedException(); } public void UpdateExtendedProperties(TeamFoundationIdentity identity) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity ReadIdentity(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string searchFactorValue, + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string searchFactorValue, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] ListApplicationGroups(string scopeId, ReadIdentityOptions readOptions, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] ListApplicationGroups(string scopeId, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue, + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[][] ReadIdentities(IdentitySearchFactor searchFactor, string[] searchFactorValues, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[][] ReadIdentities(IdentitySearchFactor searchFactor, string[] searchFactorValues, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] ReadIdentities(Guid[] teamFoundationIds, MembershipQuery queryMembership, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] ReadIdentities(Guid[] teamFoundationIds, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[][] ReadIdentities(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string[] searchFactorValues, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[][] ReadIdentities(TeamFoundation.Framework.Common.IdentitySearchFactor searchFactor, string[] searchFactorValues, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity ReadIdentity(IdentityDescriptor descriptor, MembershipQuery queryMembership, + public TeamFoundation.Framework.Client.TeamFoundationIdentity ReadIdentity(IdentityDescriptor descriptor, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public TeamFoundationIdentity[] ReadIdentities(IdentityDescriptor[] descriptors, MembershipQuery queryMembership, + public TeamFoundation.Framework.Client.TeamFoundationIdentity[] ReadIdentities(IdentityDescriptor[] descriptors, MembershipQuery queryMembership, ReadIdentityOptions readOptions, IEnumerable propertyNameFilters, IdentityPropertyScope propertyScope) { - throw new NotImplementedException(); + throw new NotSupportedException(); } } } diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 384ae242..e2c3f456 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -174,6 +175,12 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll True @@ -211,18 +218,13 @@ - - - - - + - @@ -240,6 +242,7 @@ + @@ -247,6 +250,7 @@ + @@ -301,7 +305,10 @@ + + + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/TimedContextSpecification.cs b/test/Qwiq.Core.Tests/TimedContextSpecification.cs new file mode 100644 index 00000000..f55dd949 --- /dev/null +++ b/test/Qwiq.Core.Tests/TimedContextSpecification.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using Microsoft.Qwiq.Tests.Common; + +namespace Microsoft.Qwiq.Core.Tests +{ + public abstract class TimedContextSpecification : ContextSpecification + { + private readonly IDictionary _durations = new Dictionary(StringComparer.OrdinalIgnoreCase); + + protected T TimedAction(Func action, string category, string userMessage) + { + var start = Clock.GetTimestamp(); + try + { + return action(); + } + finally + { + var stop = Clock.GetTimestamp(); + var duration = Clock.GetTimeSpan(start, stop); + Debug.Print("{0}: {1} {2}", category, duration, userMessage); + if (!_durations.ContainsKey(category)) + { + _durations[category] = TimeSpan.Zero; + } + + _durations[category] += duration; + } + } + + public override void Cleanup() + { + foreach (var category in _durations) + { + Debug.Print("{0}: {1}", category.Key, category.Value); + } + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs b/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs index dbf12864..5849a5aa 100644 --- a/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs +++ b/test/Qwiq.Core.Tests/WiqlFlatQueryTests.cs @@ -13,8 +13,8 @@ public class WiqlFlatQueryTests : IntegrationContextSpecificationSpecification public override void When() { - RestResult.WorkItem = RestResult.WorkItemStore.Query(Wiql).Single(); - SoapResult.WorkItem = SoapResult.WorkItemStore.Query(Wiql).Single(); + RestResult.WorkItem = TimedAction(()=> RestResult.WorkItemStore.Query(Wiql).Single(), "REST", "Query"); + SoapResult.WorkItem = TimedAction(() => SoapResult.WorkItemStore.Query(Wiql).Single(), "SOAP", "Query"); } } } \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs b/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs index 1a7f70f8..03e55ff9 100644 --- a/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Core.Tests/WiqlHierarchyQueryTests.cs @@ -37,15 +37,9 @@ FROM WorkItemLinks [Target].[System.WorkItemType] = 'Scenario' mode(recursive) "; - var start = Clock.GetTimestamp(); - RestResult.WorkItemLinks = RestResult.WorkItemStore.QueryLinks(WIQL).ToList(); - var stop = Clock.GetTimestamp(); - Debug.Print("REST: {0}", Clock.GetTimeSpan(start, stop)); - - start = Clock.GetTimestamp(); - SoapResult.WorkItemLinks = SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(); - stop = Clock.GetTimestamp(); - Debug.Print("SOAP: {0}", Clock.GetTimeSpan(start, stop)); + + RestResult.WorkItemLinks = TimedAction(() => RestResult.WorkItemStore.QueryLinks(WIQL).ToList(), "REST", "QueryLinks"); + SoapResult.WorkItemLinks = TimedAction(() => SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(), "SOAP", "QueryLinks"); } public override void Cleanup() diff --git a/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs index 79bf60e3..a80ad5e4 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreComparisonContextSpecification.cs @@ -1,8 +1,9 @@ using System; -using System.Diagnostics; +using System.Collections.Generic; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.Services.Client; +using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -10,7 +11,7 @@ namespace Microsoft.Qwiq.Core.Tests { - public abstract class WorkItemStoreComparisonContextSpecification : ContextSpecification + public abstract class WorkItemStoreComparisonContextSpecification : TimedContextSpecification { protected IWorkItemStore Rest { get; private set; } @@ -18,36 +19,39 @@ public abstract class WorkItemStoreComparisonContextSpecification : ContextSpeci public override void Given() { - var credentials = Credentials.CredentialsFactory.CreateCredentials((string)null); var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + var options = new AuthenticationOptions(uri, AuthenticationType.Windows){CreateCredentials = CreateCredentials}; Soap = TimedAction( - ()=> Microsoft.Qwiq.Soap.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Soap), + ()=> Microsoft.Qwiq.Soap.WorkItemStoreFactory.Instance.Create(options), "SOAP", "Create WIS"); - + options.ClientType = ClientType.Rest; Rest = TimedAction( - () => Microsoft.Qwiq.Rest.WorkItemStoreFactory.Instance.Create(uri, credentials, ClientType.Rest), + () => Microsoft.Qwiq.Soap.WorkItemStoreFactory.Instance.Create(options), "REST", "Create WIS"); } - protected static T TimedAction(Func action, string category, string userMessage) + private static IEnumerable CreateCredentials(AuthenticationType t) + { + // User did not specify a username or a password, so use the process identity + yield return new VssClientCredentials(new WindowsCredential(false)) { Storage = new VssClientCredentialStorage(), PromptType = CredentialPromptType.DoNotPrompt }; + + // Use the Windows identity of the logged on user + yield return new VssClientCredentials(true) { Storage = new VssClientCredentialStorage(), PromptType = CredentialPromptType.PromptIfNeeded }; + } + + public override void Cleanup() { - var start = Clock.GetTimestamp(); - try - { - return action(); - } - finally - { - var stop = Clock.GetTimestamp(); - Debug.Print("{0}: {1} {2}", category, Clock.GetTimeSpan(start, stop), userMessage); - } + Rest?.Dispose(); + Soap?.Dispose(); + + base.Cleanup(); } } diff --git a/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs index 7cfccf7b..fea87fae 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreFactoryTests.cs @@ -11,72 +11,73 @@ namespace Microsoft.Qwiq.Core.Tests { - public abstract class WorkItemStoreFactoryContextSpecification : ContextSpecification + public abstract class WorkItemStoreFactoryContextSpecification : WorkItemStoreTests { protected IWorkItemStoreFactory Instance { get; private set; } - protected IWorkItemStore Store { get; set; } public override void Given() { - base.Given(); Instance = WorkItemStoreFactory.Instance; + base.Given(); } public override void Cleanup() { base.Cleanup(); - Store?.Dispose(); + WorkItemStore?.Dispose(); } - } - [TestClass] - public class Given_a_Uri_and_Credential : WorkItemStoreFactoryContextSpecification - { - public override void When() + protected override IWorkItemStore Create() { var uri = new Uri("https://microsoft.visualstudio.com/DefaultCollection"); var cred = new VssClientCredentials( - new WindowsCredential(true), - CredentialPromptType.PromptIfNeeded) + new WindowsCredential(true), + CredentialPromptType.PromptIfNeeded) { Storage = new VssClientCredentialStorage() }; #pragma warning disable CS0618 // Type or member is obsolete - Store = Instance.Create( + return Instance.Create( uri, new TfsCredentials(cred)); #pragma warning restore CS0618 // Type or member is obsolete } + } + [TestClass] + public class Given_a_Uri_and_Credential : WorkItemStoreFactoryContextSpecification + { [TestMethod] [TestCategory("localOnly")] public void Store_is_Created() { - Store.ShouldNotBeNull(); + WorkItemStore.ShouldNotBeNull(); } } [TestClass] public class Given_a_Uri_and_Credentials_from_the_CredentialsFactory : WorkItemStoreFactoryContextSpecification { - public override void When() + protected override IWorkItemStore Create() { var uri = new Uri("https://microsoft.visualstudio.com/DefaultCollection"); #pragma warning disable CS0618 // Type or member is obsolete - Store = Instance.Create( + return Instance.Create( uri, CredentialsFactory.CreateCredentials((string)null)); #pragma warning restore CS0618 // Type or member is obsolete } + + [TestMethod] [TestCategory("localOnly")] public void Store_is_Created() { - Store.ShouldNotBeNull(); + WorkItemStore.ShouldNotBeNull(); } } } diff --git a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs index 5bf1be1b..e6107346 100644 --- a/test/Qwiq.Core.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemStoreTests.cs @@ -166,16 +166,17 @@ protected override IWorkItemStore Create() } } - public abstract class WorkItemStoreTests : ContextSpecification + public abstract class WorkItemStoreTests : TimedContextSpecification where T : IWorkItemStore { internal IQueryFactory QueryFactory; - protected IWorkItemStore WorkItemStore; + protected T WorkItemStore; public override void Cleanup() { - WorkItemStore.Dispose(); + WorkItemStore?.Dispose(); + base.Cleanup(); } public override void Given() diff --git a/test/Qwiq.Core.Tests/WorkItemTests.cs b/test/Qwiq.Core.Tests/WorkItemTests.cs new file mode 100644 index 00000000..50e3123f --- /dev/null +++ b/test/Qwiq.Core.Tests/WorkItemTests.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Qwiq.Credentials; +using Microsoft.Qwiq.Rest; +using Microsoft.VisualStudio.Services.Client; +using Microsoft.VisualStudio.Services.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Core.Tests +{ + public abstract class RestWorkItemContextSpecification : WorkItemContextSpecification + { + + + protected override IWorkItemStore Create() + { + var options = AuthenticationOptions; + options.ClientType = ClientType.Rest; + + return TimedAction(() => Rest.WorkItemStoreFactory.Instance.Create(options), "REST", "WIS Create"); + } + } + + public abstract class SoapWorkItemContextSpecification : WorkItemContextSpecification + { + + + protected override IWorkItemStore Create() + { + var options = AuthenticationOptions; + options.ClientType = ClientType.Soap; + + return TimedAction(() => Soap.WorkItemStoreFactory.Instance.Create(options), "SOAP", "WIS Create"); + } + } + + public abstract class WorkItemContextSpecification : WorkItemStoreTests + where T : IWorkItemStore + { + private const int Id = 10726528; + + protected AuthenticationOptions AuthenticationOptions + { + get + { + var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + var options = new AuthenticationOptions(uri, AuthenticationType.Windows) { CreateCredentials = CreateCredentials }; + return options; + } + } + + private static IEnumerable CreateCredentials(AuthenticationType t) + { + // User did not specify a username or a password, so use the process identity + yield return new VssClientCredentials(new WindowsCredential(false)) { Storage = new VssClientCredentialStorage(), PromptType = CredentialPromptType.DoNotPrompt }; + + // Use the Windows identity of the logged on user + yield return new VssClientCredentials(true) { Storage = new VssClientCredentialStorage(), PromptType = CredentialPromptType.PromptIfNeeded }; + } + + protected IWorkItem Result { get; private set; } + + public override void When() + { + Result = WorkItemStore.Query(Id); + } + + [TestMethod] + [TestCategory("localOnly")] + public void Reading_Id_from_this_operator_with_ReferenceName_equals_the_property_value() + { + Result[CoreFieldRefNames.Id]?.ToString().ShouldEqual(Result.Id.ToString()); + } + + [TestMethod] + [TestCategory("localOnly")] + public void Reading_Id_from_Fields_property_with_ReferenceName_equals_the_property_value() + { + Result.Fields[CoreFieldRefNames.Id]?.Value?.ToString().ShouldEqual(Result.Id.ToString()); + } + } + + [TestClass] + public class Given_a_WorkItem_from_REST : RestWorkItemContextSpecification + { + + } + + [TestClass] + public class Given_a_WorkItem_from_SOAP : RestWorkItemContextSpecification + { + + } +} diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index 5f244b9a..cb40f129 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -13,6 +13,8 @@ + + diff --git a/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs b/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs index 4d25301f..5ddc82fa 100644 --- a/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs +++ b/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs @@ -50,7 +50,7 @@ public class Given_an_alias_which_can_be_resolved_by_identity_descriptor : Singu public override void Given() { ExpectedIdentity = MockIdentityManagementService.Chrisj; - Alias = ExpectedIdentity.UniqueName; + Alias = ExpectedIdentity.GetUserAlias(); base.Given(); } } @@ -104,8 +104,8 @@ public override void Given() var contestant2 = MockIdentityManagementService.Danj; ExpectedIdentities = new Dictionary { - { contestant1.UniqueName, contestant1 }, - { contestant2.UniqueName, contestant2 } + { contestant1.GetUserAlias(), contestant1 }, + { contestant2.GetUserAlias(), contestant2 } }; Aliases = ExpectedIdentities.Keys.ToArray(); base.Given(); @@ -150,7 +150,7 @@ public override void Given() var danj = MockIdentityManagementService.Chrisj; ExpectedIdentities = new Dictionary { - { danj.UniqueName, danj }, + { danj.GetUserAlias(), danj }, { KnownSearchAliasForChrisjoh, MockIdentityManagementService.Chrisjoh }, { UnknownAliasB, null } }; diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index b94acd70..b1a91420 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -39,6 +40,12 @@ 4 + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + ..\..\packages\Should.1.1.20\lib\Should.dll @@ -50,11 +57,7 @@ - - - - - + @@ -115,6 +118,9 @@ + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index f4e9d3ed..7195e387 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -2,5 +2,7 @@ + + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index fd8e7562..206eac05 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -40,6 +41,12 @@ 4 + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + ..\..\packages\Should.1.1.20\lib\Should.dll @@ -51,11 +58,7 @@ - - - - - + @@ -112,6 +115,9 @@ + + + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index f4e9d3ed..7195e387 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -2,5 +2,7 @@ + + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 885daae4..47266910 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -14,26 +14,9 @@ public InstrumentedMockWorkItemStore(IWorkItemStore innerWorkItemStore) _innerWorkItemStore = innerWorkItemStore; } - public TfsCredentials AuthorizedCredentials => _innerWorkItemStore.AuthorizedCredentials; - - public IEnumerable Projects - { - get - { - ProjectsCallCount += 1; - return _innerWorkItemStore.Projects; - } - } - public int ProjectsCallCount { get; private set; } - public int QueryCallCount - { - get - { - return QueryIdCallCount + QueryIdsCallCount + QueryLinksCallCount + QueryStringCallCount; - } - } + public int QueryCallCount => QueryIdCallCount + QueryIdsCallCount + QueryLinksCallCount + QueryStringCallCount; public int QueryIdCallCount { get; private set; } @@ -43,6 +26,25 @@ public int QueryCallCount public int QueryStringCallCount { get; private set; } + public int TeamProjectCollectionCallCount { get; private set; } + + public int WorkItemLinkTypesCallCount { get; private set; } + + public TfsCredentials AuthorizedCredentials => _innerWorkItemStore.AuthorizedCredentials; + + public ClientType ClientType => _innerWorkItemStore.ClientType; + + public IFieldDefinitionCollection FieldDefinitions => _innerWorkItemStore.FieldDefinitions; + + public IEnumerable Projects + { + get + { + ProjectsCallCount += 1; + return _innerWorkItemStore.Projects; + } + } + public ITfsTeamProjectCollection TeamProjectCollection { get @@ -52,13 +54,11 @@ public ITfsTeamProjectCollection TeamProjectCollection } } - public int TeamProjectCollectionCallCount { get; private set; } - public TimeZone TimeZone => _innerWorkItemStore.TimeZone; - public string UserDisplayName => _innerWorkItemStore.UserDisplayName; + public string UserAccountName => _innerWorkItemStore.UserAccountName; - public string UserIdentityName => _innerWorkItemStore.UserIdentityName; + public string UserDisplayName => _innerWorkItemStore.UserDisplayName; public string UserSid => _innerWorkItemStore.UserSid; @@ -71,8 +71,6 @@ public WorkItemLinkTypeCollection WorkItemLinkTypes } } - public int WorkItemLinkTypesCallCount { get; private set; } - public void Dispose() { _innerWorkItemStore.Dispose(); @@ -101,7 +99,5 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision QueryLinksCallCount += 1; return _innerWorkItemStore.QueryLinks(wiql, dayPrecision); } - - public IFieldDefinitionCollection FieldDefinitions => _innerWorkItemStore.FieldDefinitions; } } \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 3592f738..944afd97 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -40,6 +41,12 @@ 4 + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + ..\..\packages\Should.1.1.20\lib\Should.dll True @@ -128,8 +135,11 @@ + + + - \ No newline at end of file diff --git a/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs b/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs deleted file mode 100644 index caa33df6..00000000 --- a/test/Qwiq.Relatives.Tests/RelativesAwareTeamFoundationServerWorkItemQueryProviderTests.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Should; -using Microsoft.Qwiq.Linq; -using Microsoft.Qwiq.Linq.Visitors; -using Microsoft.Qwiq.Mapper; -using Microsoft.Qwiq.Mapper.Attributes; -using Microsoft.Qwiq.Mocks; -using Microsoft.Qwiq.Relatives.Linq; -using Microsoft.Qwiq.Relatives.Mapper; -using Microsoft.Qwiq.Relatives.Tests.Mocks; -using Microsoft.Qwiq.Tests.Common; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.Qwiq.Relatives.Tests -{ - public abstract class RelativesAwareTeamFoundationServerWorkItemQueryProviderContextSpecification : ContextSpecification - { - protected IEnumerable WorkItemStoreWorkItems; - protected IEnumerable WorkItemStoreWorkItemLinks; - protected Query Query; - protected IEnumerable>> Actual; - - public override void Given() - { - var workItemStore = new MockWorkItemStore(WorkItemStoreWorkItems, WorkItemStoreWorkItemLinks); - var fieldMapper = new CachingFieldMapper(new FieldMapper()); - var propertyReflector = new PropertyReflector(); - var propertyInspector = new PropertyInspector(propertyReflector); - var builder = new WiqlQueryBuilder(new RelativesAwareWiqlTranslator(fieldMapper), new PartialEvaluator(), new RelativesAwareQueryRewriter()); - var mapperStrategies = new IWorkItemMapperStrategy[] - { - new AttributeMapperStrategy(propertyInspector, - TypeParser.Default), - new WorkItemLinksMapperStrategy(propertyInspector, workItemStore), - new ParentIdMapperStrategy(workItemStore) - }; - var mapper = new WorkItemMapper(mapperStrategies); - var queryProvider = new RelativesAwareTeamFoundationServerWorkItemQueryProvider(workItemStore, builder, mapper, fieldMapper); - - Query = new Query(queryProvider, builder); - } - } - - public abstract class QueryReturnsResults : RelativesAwareTeamFoundationServerWorkItemQueryProviderContextSpecification - { - public override void Given() - { - var wit = new MockWorkItemType( - "SimpleMockWorkItem", - new [] - { - CoreFieldDefinitions.ReferenceNameLookup[CoreFieldRefNames.Id], - CoreFieldDefinitions.ReferenceNameLookup[CoreFieldRefNames.WorkItemType], - MockFieldDefinition.Create("Priority"), - }); - - WorkItemStoreWorkItems = new List - { - new MockWorkItem(wit, new Dictionary - { - {"ID", 1}, - {"Priority", 2} - }), - new MockWorkItem(wit, new Dictionary - { - {"ID", 2}, - {"Priority", 4} - }), - new MockWorkItem(wit, new Dictionary - { - {"ID", 3}, - {"Priority", 3} - }), - new MockWorkItem(wit, new Dictionary - { - {"ID", 4}, - {"Priority", 4} - }), - new MockWorkItem(wit, new Dictionary - { - {"ID", 5}, - {"Priority", 5} - }) - }; - - WorkItemStoreWorkItemLinks = new[] { - new MockWorkItemLinkInfo(0, 3), - new MockWorkItemLinkInfo(3, 1), - new MockWorkItemLinkInfo(3, 2), - new MockWorkItemLinkInfo(0, 4), - new MockWorkItemLinkInfo(0, 5) - }; - - base.Given(); - } - } - - public abstract class QueryDoesNotReturnResults : RelativesAwareTeamFoundationServerWorkItemQueryProviderContextSpecification - { - public override void Given() - { - WorkItemStoreWorkItems = Enumerable.Empty(); - WorkItemStoreWorkItemLinks = Enumerable.Empty(); - base.Given(); - } - } - - [TestClass] - public class given_a_query_provider_when_a_parents_clause_is_used : QueryReturnsResults - { - public override void When() - { - Actual = Query.Parents(); - } - - [TestMethod] - public void item_with_id_of_1_has_a_parent_of_id_3() - { - Actual.Single(kvp => kvp.Key.Id == 3).Value.Count(wi => wi.Id == 1).ShouldEqual(1); - } - - [TestMethod] - public void item_with_id_of_5_has_no_parent() - { - Actual.Single(kvp => kvp.Key.Id == 5).Value.ShouldBeEmpty(); - } - } - - [TestClass] - public class given_a_query_provider_when_a_children_clause_is_used : QueryReturnsResults - { - public override void When() - { - Actual = Query.Children(); - } - - [TestMethod] - public void item_with_id_of_3_has_a_child_of_id_2() - { - Actual.Single(kvp => kvp.Key.Id == 3).Value.Count(wi => wi.Id == 2).ShouldEqual(1); - } - - [TestMethod] - public void item_with_id_of_5_has_no_children() - { - Actual.Single(kvp => kvp.Key.Id == 5).Value.ShouldBeEmpty(); - } - } - - - [TestClass] - public class when_a_parents_clause_is_used_on_a_query_with_no_results : QueryDoesNotReturnResults - { - public override void When() - { - Actual = Query.Parents(); - } - - [TestMethod] - public void an_empty_set_is_returned() - { - Actual.ShouldBeEmpty(); - } - } - - [TestClass] - public class when_a_children_clause_is_used_on_a_query_with_no_results : QueryDoesNotReturnResults - { - public override void When() - { - Actual = Query.Children(); - } - - [TestMethod] - public void an_empty_set_is_returned() - { - Actual.ShouldBeEmpty(); - } - } - - [TestClass] - public class given_a_query_provider_when_a_relatives_clause_is_used_with_a_type_with_no_workitemtype : QueryReturnsResults - { - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void children_clause_causes_InvalidOperationException() - { - var result = Query.Children().ToList(); - } - - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void parents_clause_causes_InvalidOperationException() - { - var result = Query.Parents().ToList(); - } - } - - [TestClass] - public class given_a_query_provider_when_a_relatives_clause_is_used_with_a_type_with_multiple_workitemtypes : QueryReturnsResults - { - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void children_clause_causes_InvalidOperationException() - { - var result = Query.Children().ToList(); - } - - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void parents_clause_causes_InvalidOperationException() - { - var result = Query.Parents().ToList(); - } - } -} - diff --git a/test/Qwiq.Relatives.Tests/app.config b/test/Qwiq.Relatives.Tests/app.config deleted file mode 100644 index 871b1c0f..00000000 --- a/test/Qwiq.Relatives.Tests/app.config +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Relatives.Tests/packages.config b/test/Qwiq.Relatives.Tests/packages.config deleted file mode 100644 index 7195e387..00000000 --- a/test/Qwiq.Relatives.Tests/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/ContextSpecification.cs b/test/Qwiq.Tests.Common/ContextSpecification.cs index 793cb6cb..66606ad4 100644 --- a/test/Qwiq.Tests.Common/ContextSpecification.cs +++ b/test/Qwiq.Tests.Common/ContextSpecification.cs @@ -112,7 +112,7 @@ namespace Microsoft.Qwiq.Tests.Common /// http://channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DPR302 /// /// - //[DebuggerStepThrough] + [DebuggerStepThrough] public abstract class ContextSpecification : IContextSpecification { [TestInitialize] @@ -123,14 +123,29 @@ public void TestInitialize() Given(); When(); } - catch (Exception) + catch (Exception e) { // This is very, very bad. + Debug.Print($"{this.GetType().FullName} encountered an exception during Given/When: {e.Message}\r\n{e}"); - if (!Debugger.IsAttached) Debugger.Launch(); - if (Debugger.IsAttached) Debugger.Break(); + try + { + Cleanup(); + } + catch (Exception ex) + { + Debug.Print($"{GetType().FullName} encountered a problem during cleanup: {ex.Message}\r\n{ex}"); + } - Cleanup(); + if (!Debugger.IsAttached) + { + Assert.Fail($"{this.GetType().FullName} encountered an exception: {e.Message}"); + } + else + { + Debugger.Break(); + Assert.Fail($"{this.GetType().FullName} encountered an exception: {e.Message}"); + } } } From 1a6305ae10114ce85fdc32f66398b71d2ac0fc51 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 14 Apr 2017 10:37:13 -0700 Subject: [PATCH 098/251] Code cleanup --- src/Qwiq.Core/RegisteredLinkTypeCollection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs index 087cecce..4f8a7526 100644 --- a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs @@ -5,8 +5,6 @@ namespace Microsoft.Qwiq { public class RegisteredLinkTypeCollection : ReadOnlyList, IRegisteredLinkTypeCollection { - - public RegisteredLinkTypeCollection(IEnumerable linkTypes) : base(linkTypes, type => type.Name) { From 64d368eb39871c1aeaf038282981ce3b7012c249 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 14 Apr 2017 15:47:46 -0700 Subject: [PATCH 099/251] Update Benchmarks Benchmarks are now separated into two namespaces: the actual benchmark and a test that exercises the benchmark code path. The former cannot be executed under Debug and uses a special test context to enforce constraints. The latter uses the standard test context and exercises the benchmark code path to ensure it remains functional. --- src/Qwiq.Core/IWorkItemCommon.cs | 2 +- src/Qwiq.Core/WorkItemCommon.cs | 2 +- src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs | 20 +- .../BenchmarkContextSpecification.cs | 24 + test/Qwiq.Benchmark/Constants.cs | 18 + test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 7 + test/Qwiq.Benchmark/Randomizer.cs | 44 +- test/Qwiq.Benchmark/WorkItemGenerator.cs | 178 +++++- test/Qwiq.Benchmark/WorkItemLinkGenerator.cs | 74 +++ .../Benchmark.cs | 117 ++-- .../Qwiq.Identity.Benchmark.Tests.csproj | 4 + test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs | 258 ++++++++ .../PerformanceTests.cs | 556 ------------------ .../PocoMapping.cs | 91 +++ .../PocoMappingWithLinks.cs | 99 ++++ .../Qwiq.Mapper.Benchmark.Tests.csproj | 5 +- .../SubMockModel.cs | 9 + test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 2 +- test/Qwiq.Mocks/Extensions.cs | 31 +- test/Qwiq.Mocks/MockWorkItemStore.cs | 134 +++-- 20 files changed, 944 insertions(+), 731 deletions(-) create mode 100644 test/Qwiq.Benchmark/BenchmarkContextSpecification.cs create mode 100644 test/Qwiq.Benchmark/Constants.cs create mode 100644 test/Qwiq.Benchmark/WorkItemLinkGenerator.cs create mode 100644 test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs delete mode 100644 test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs create mode 100644 test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs create mode 100644 test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs create mode 100644 test/Qwiq.Mapper.Benchmark.Tests/SubMockModel.cs diff --git a/src/Qwiq.Core/IWorkItemCommon.cs b/src/Qwiq.Core/IWorkItemCommon.cs index 58520e11..75be2d4d 100644 --- a/src/Qwiq.Core/IWorkItemCommon.cs +++ b/src/Qwiq.Core/IWorkItemCommon.cs @@ -94,6 +94,6 @@ public interface IWorkItemCommon : IWorkItemCore string WorkItemType { get; } - // Team Project? + string TeamProject { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index 62d54410..69b63c7e 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -97,7 +97,7 @@ public virtual string Title public virtual string WorkItemType => GetValue(CoreFieldRefNames.WorkItemType); - + public virtual string TeamProject => GetValue(CoreFieldRefNames.TeamProject); public bool Equals(IWorkItemCommon other) { diff --git a/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs b/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs index 75ade197..90fba7fa 100644 --- a/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs +++ b/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs @@ -6,17 +6,17 @@ namespace Microsoft.Qwiq.Mapper { public abstract class WorkItemMapperStrategyBase : IWorkItemMapperStrategy { - public virtual void Map(Type targetWorkItemType, IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) + public virtual void Map( + Type targetWorkItemType, + IEnumerable> workItemMappings, + IWorkItemMapper workItemMapper) { foreach (var workItemMapping in workItemMappings) - { Map(targetWorkItemType, workItemMapping.Key, workItemMapping.Value, workItemMapper); - } } - public virtual void Map( - IEnumerable> workItemMappings, - IWorkItemMapper workItemMapper) where T : IIdentifiable, new() + public virtual void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) + where T : IIdentifiable, new() { Map(typeof(T), workItemMappings.Select(s => new KeyValuePair(s.Key, s.Value)), workItemMapper); } @@ -27,10 +27,7 @@ protected virtual void Map( IIdentifiable targetWorkItem, IWorkItemMapper workItemMapper) { - Map( - targetWorkItemType, - new[] { new KeyValuePair(sourceWorkItem, targetWorkItem) }, - workItemMapper); + Map(targetWorkItemType, new[] { new KeyValuePair(sourceWorkItem, targetWorkItem) }, workItemMapper); } protected virtual void Map(IWorkItem sourceWorkItem, T targetWorkItem, IWorkItemMapper workItemMapper) @@ -38,5 +35,4 @@ protected virtual void Map(IWorkItem sourceWorkItem, T targetWorkItem, IWorkI Map(typeof(T), sourceWorkItem, (IIdentifiable)targetWorkItem, workItemMapper); } } -} - +} \ No newline at end of file diff --git a/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs b/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs new file mode 100644 index 00000000..5f86fb1c --- /dev/null +++ b/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs @@ -0,0 +1,24 @@ +using System.Diagnostics; + +using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Qwiq.Benchmark +{ + public abstract class BenchmarkContextSpecification : ContextSpecification + { + /// + public override void Given() + { + if (Debugger.IsAttached) + { + Assert.Fail("Never should use an attached debugger (e.g. Visual Studio or WinDbg) during the benchmarking."); + } + +#if DEBUG + Assert.Fail("Never use the Debug build for benchmarking. Never. The debug version of the target method can run 10–100 times slower."); +#endif + + } + } +} diff --git a/test/Qwiq.Benchmark/Constants.cs b/test/Qwiq.Benchmark/Constants.cs new file mode 100644 index 00000000..0a5f95a5 --- /dev/null +++ b/test/Qwiq.Benchmark/Constants.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Qwiq.Benchmark +{ + public static class Constants + { + public static class TestCategory + { + public const string Benchmark = "Benchmark"; + + public const string Performance = "Performance"; + } + } +} diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 82510713..e7d761bf 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -161,9 +161,12 @@ + + + @@ -179,6 +182,10 @@ {DB07E690-4B77-414F-91C7-1A48C9F01F24} Qwiq.Mocks + + {B45C92B0-AC36-409D-86A5-5428C87384C3} + Qwiq.Tests.Common + diff --git a/test/Qwiq.Benchmark/Randomizer.cs b/test/Qwiq.Benchmark/Randomizer.cs index ef457fc6..ac9cee3d 100644 --- a/test/Qwiq.Benchmark/Randomizer.cs +++ b/test/Qwiq.Benchmark/Randomizer.cs @@ -8,29 +8,49 @@ public class Randomizer : Random public static Randomizer Instance => random ?? (random = new Randomizer()); - public static bool ShouldEnter() + + } + + public static class RandomizerExtensions + { + public static int NextSystemId(this Randomizer instance) + { + return instance.NextSystemId(int.MaxValue); + } + + public static int NextSystemId(this Randomizer instance, int max) + { + return instance.NextSystemId(1, max); + } + + public static int NextSystemId(this Randomizer instance, int min, int max) { - return Instance.NextDouble() < 0.5; + return instance.Next(min, max); } - public static int NextInt32() + /// + /// Taken from http://stackoverflow.com/questions/609501/generating-a-random-decimal-in-c-sharp Jon Skeet's answer + /// + public static decimal NextDecimal(this Randomizer instance) + { + var scale = (byte)instance.Next(29); + var sign = instance.Next(2) == 1; + return new decimal(instance.NextInt32(), instance.NextInt32(), instance.NextInt32(), sign, scale); + } + + public static int NextInt32(this Randomizer instance) { unchecked { - var firstBits = Instance.Next(0, 1 << 4) << 28; - var lastBits = Instance.Next(0, 1 << 28); + var firstBits = instance.Next(0, 1 << 4) << 28; + var lastBits = instance.Next(0, 1 << 28); return firstBits | lastBits; } } - /// - /// Taken from http://stackoverflow.com/questions/609501/generating-a-random-decimal-in-c-sharp Jon Skeet's answer - /// - public static decimal NextDecimal() + public static bool ShouldEnter(this Randomizer instance) { - var scale = (byte)Instance.Next(29); - var sign = Instance.Next(2) == 1; - return new decimal(NextInt32(), NextInt32(), NextInt32(), sign, scale); + return instance.NextDouble() < 0.5; } } } \ No newline at end of file diff --git a/test/Qwiq.Benchmark/WorkItemGenerator.cs b/test/Qwiq.Benchmark/WorkItemGenerator.cs index e09938f0..90c88f7c 100644 --- a/test/Qwiq.Benchmark/WorkItemGenerator.cs +++ b/test/Qwiq.Benchmark/WorkItemGenerator.cs @@ -15,47 +15,133 @@ public class WorkItemGenerator private readonly Func _create; - public WorkItemGenerator(Func create, IEnumerable propertiesToSkip) + private string[] _assignees + ; + + public WorkItemGenerator(Func createFunc, IEnumerable propertiesToSkip = null) { - if (propertiesToSkip == null) throw new ArgumentNullException(nameof(propertiesToSkip)); + _create = createFunc ?? throw new ArgumentNullException(nameof(createFunc)); + _propertiesToSkip = propertiesToSkip == null + ? new HashSet(StringComparer.OrdinalIgnoreCase) + : new HashSet(propertiesToSkip, StringComparer.OrdinalIgnoreCase); + + // Add properties that have special cases + _propertiesToSkip.Add("Id"); + + // Calculated fields + _propertiesToSkip.Add("HyperLinkCount"); + _propertiesToSkip.Add("RelatedLinkCount"); + _propertiesToSkip.Add("ExternalLinkCount"); + _propertiesToSkip.Add("AttachedFileCount"); + + _propertiesToSkip.Add("Type"); + _propertiesToSkip.Add("WorkItemType"); + + _propertiesToSkip.Add("TeamProject"); + + // + _propertiesToSkip.Add("IsDirty"); + _propertiesToSkip.Add("Rev"); + _propertiesToSkip.Add("Revision"); + _propertiesToSkip.Add("RevisedDate"); + _propertiesToSkip.Add("History"); + _propertiesToSkip.Add("Watermark"); + + //// Identity fields + //_propertiesToSkip.Add("AssignedTo"); + //_propertiesToSkip.Add("ChangedBy"); + //_propertiesToSkip.Add("CreatedBy"); - _propertiesToSkip = new HashSet(propertiesToSkip, StringComparer.OrdinalIgnoreCase); - _create = create ?? throw new ArgumentNullException(nameof(create)); + _assignees = new[] + { + MockIdentityManagementService.Danj.DisplayName, + MockIdentityManagementService.Adamb.DisplayName, + MockIdentityManagementService.Chrisj.DisplayName, + MockIdentityManagementService.Chrisjoh.DisplayName, + MockIdentityManagementService.Chrisjohn.DisplayName, + MockIdentityManagementService.Chrisjohns.DisplayName + }; } - public IList Generate(int quantity = 500) + public IReadOnlyCollection Generate(int quantity = 50) { - Items = new List(quantity); + // After generating the parent/child links, this can grow an order of magnitude + var items = new List(quantity * 10); var generatedItems = new HashSet(); + int GenerateUnusedWorkItemId() + { + // ID needs to be populated prior to other properties (as they may depend on that value) + var id = Randomizer.Instance.NextSystemId(quantity); + while (generatedItems.Contains(id)) + { + id = Randomizer.Instance.NextSystemId(quantity * 10); + } + + return id; + } + for (var i = 0; i < quantity; i++) { - var instance = _create(); - PopulatePropertiesOnInstance(instance); + GenerateItem(_create, GenerateUnusedWorkItemId, generatedItems, items); + } + + Items = items.AsReadOnly(); + return Items; + } + + // Generates an item and link references + private T GenerateItem(Func createFunc, Func idFunc, ISet generatedItems, ICollection items) + { + var instance = GenerateItem(createFunc, idFunc); + + if (generatedItems.Contains(instance.Id)) + { + // Item has already been generated + var id = instance.Id; + return items.Single(p => p.Id == id); + } + + + items.Add(instance); + generatedItems.Add(instance.Id); + + foreach (var link in instance.Links.OfType().ToArray()) + { + var linked = default(T); - if (!generatedItems.Contains(instance.Id)) + if (!generatedItems.Contains(link.RelatedWorkItemId)) { - generatedItems.Add(instance.Id); - Items.Add(instance); + linked = GenerateItem(createFunc, () => link.RelatedWorkItemId, generatedItems, items); } - foreach (var link in instance.Links.OfType()) + // Determine if we need to create a recipricol link + if (!(link.LinkTypeEnd?.LinkType.IsDirectional ?? false)) continue; + + // Look up the item if it was not previously generated + if (linked == null) { - if (!generatedItems.Contains(link.RelatedWorkItemId)) - { - instance = _create(); - PopulatePropertiesOnInstance(instance); - instance[CoreFieldRefNames.Id] = link.RelatedWorkItemId; - Items.Add(instance); - generatedItems.Add(instance.Id); - } + linked = items.Single(p => p.Id == link.RelatedWorkItemId); } + + // Add the recipricol link + linked.Links.Add(linked.CreateRelatedLink(instance.Id, link.LinkTypeEnd.OppositeEnd)); } - return Items; + return instance; } - public IList Items { get; private set; } + /// Generates a single item + private T GenerateItem(Func createFunc, Func idFunc) + { + var instance = createFunc(); + var id = idFunc(); + instance[CoreFieldRefNames.Id] = id; + PopulatePropertiesOnInstance(instance); + return instance; + } + + public IReadOnlyCollection Items { get; private set; } private void PopulatePropertiesOnInstance(T instance) { @@ -69,7 +155,7 @@ var property in // If we should skip the source property if (_propertiesToSkip.Contains(property.Name)) continue; - var value = GetRandomValue(property.PropertyType); + var value = GetRandomValue(instance, property.Name, property.PropertyType); try { property.SetValue(instance, value); @@ -89,7 +175,7 @@ var property in protected const string Chars = "$%#@!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&"; - protected virtual object GetRandomValue(Type propertyType) + protected virtual object GetRandomValue(T instance, string propertyName, Type propertyType) { var randomizer = Randomizer.Instance; @@ -98,17 +184,47 @@ protected virtual object GetRandomValue(Type propertyType) switch (propertyType.ToString()) { case "System.Int32": - value = Randomizer.NextInt32(); + value = Randomizer.Instance.NextSystemId(); + break; + case "System.Nullable`1[System.Int32]": + if (randomizer.ShouldEnter()) + { + value = randomizer.NextSystemId(); + } + else + { + value = null; + } break; - case "System.String": - value = - new string( - Enumerable.Repeat(Chars, randomizer.Next(5, 250)) - .Select(s => s[randomizer.Next(s.Length)]) - .ToArray()); + if (StringComparer.OrdinalIgnoreCase.Equals("AssignedTo", propertyName) + || StringComparer.OrdinalIgnoreCase.Equals("ChangedBy", propertyName) + || StringComparer.OrdinalIgnoreCase.Equals("CreatedBy", propertyName)) + { + var i = Randomizer.Instance.Next(0, _assignees.Length - 1); + value = _assignees[i]; + } + else + { + value = new string( + Enumerable.Repeat(Chars, randomizer.Next(5, 250)) + .Select(s => s[randomizer.Next(s.Length)]) + .ToArray()); + } break; + case "System.Nullable`1[System.DateTime]": + if (!Randomizer.Instance.ShouldEnter()) + { + value = null; + } + else + { + var range2 = DateTime.MaxValue - DateTime.MinValue; + var randTimeSpan2 = new TimeSpan((long)(randomizer.NextDouble() * range2.Ticks)); + value = DateTime.MinValue + randTimeSpan2; + } + break; case "System.DateTime": var range = DateTime.MaxValue - DateTime.MinValue; var randTimeSpan = new TimeSpan((long)(randomizer.NextDouble() * range.Ticks)); diff --git a/test/Qwiq.Benchmark/WorkItemLinkGenerator.cs b/test/Qwiq.Benchmark/WorkItemLinkGenerator.cs new file mode 100644 index 00000000..9f8d4b59 --- /dev/null +++ b/test/Qwiq.Benchmark/WorkItemLinkGenerator.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Qwiq; + +namespace Qwiq.Benchmark +{ + public class WorkItemLinkGenerator : WorkItemGenerator + where T : IWorkItem + { + private readonly Func _linkFunc; + + private readonly IWorkItemLinkType _linkType; + + public WorkItemLinkGenerator( + Func createFunc, + IWorkItemLinkType linkType, + Func linkFunc, + IEnumerable propertiesToSkip = null) + : base(createFunc, propertiesToSkip) + { + _linkFunc = linkFunc ?? throw new ArgumentNullException(nameof(linkFunc)); + _linkType = linkType ?? throw new ArgumentNullException(nameof(linkType)); + } + + protected override object GetRandomValue(T instance, string propertyName, Type propertyType) + { + switch (propertyType.ToString()) + { + case "System.Collections.Generic.ICollection`1[Microsoft.Qwiq.ILink]": + var retval = new List(); + + if (Randomizer.Instance.ShouldEnter()) + { + // Create a random set of child links + var childLinkCount = Randomizer.Instance.Next(1, 3); + for (var i = 0; i < childLinkCount; i++) + { + var s = instance.Id; + var t = Randomizer.Instance.NextSystemId(s, int.MaxValue); + + while (s == t) + { + t = Randomizer.Instance.NextSystemId(int.MaxValue); + } + + retval.Add(_linkFunc(_linkType.ForwardEnd, s, t)); + } + } + + // Randomly create a parent link + if (Randomizer.Instance.ShouldEnter()) + { + var s = instance.Id; + // The target is constrained here to create higher density observed in actual work item structures + var t = Randomizer.Instance.NextSystemId(1, 36); + + while (s == t) + { + t = Randomizer.Instance.NextSystemId(1, 36); + } + + retval.Add(_linkFunc(_linkType.ReverseEnd, s, t)); + } + + //TODO: Generate external links (Branch, Build, Fixed in Changeset, Fixed in Commit, Hyperlink, Pull Request + return retval; + + default: + return base.GetRandomValue(instance, propertyName, propertyType); + } + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs index dc364a7c..e52df587 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs +++ b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs @@ -8,80 +8,91 @@ using Microsoft.Qwiq.Mapper; using Microsoft.Qwiq.Mapper.Attributes; using Microsoft.Qwiq.Mocks; +using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Qwiq.Benchmark; using Qwiq.Identity.Tests.Mocks; +using B = Microsoft.Qwiq.Identity.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_a_BulkIdentityAwareAttributeMapperStrategy; + namespace Microsoft.Qwiq.Identity.Benchmark.Tests { - [Config(typeof(BenchmarkConfig))] [TestClass] - public class Benchmark + public class BENCHMARK_Given_a_set_of_WorkItems_with_a_BulkIdentityAwareAttributeMapperStrategy : BenchmarkContextSpecification { - private IWorkItemMapperStrategy _strategy; - private IEnumerable> _workItemMappings; + /// + public override void When() + { + BenchmarkRunner.Run(); + } - [Setup] - [TestInitialize] - public void Setup() + [TestMethod] + [TestCategory(Constants.TestCategory.Benchmark)] + [TestCategory(Constants.TestCategory.Performance)] + [TestCategory("localOnly")] + public void Execute_Identity_Mapping_Performance_Benchmark() { - var propertyInspector = new PropertyInspector(new PropertyReflector()); - _strategy = new BulkIdentityAwareAttributeMapperStrategy( - propertyInspector, - new MockIdentityManagementService() - ); - - var generator = new WorkItemGenerator(() => new MockWorkItem(), new[] { "Revisions", "Item", "AssignedTo" }); - generator.Generate(); - - var assignees = new[] - { - MockIdentityManagementService.Danj.DisplayName, - MockIdentityManagementService.Adamb.DisplayName, - MockIdentityManagementService.Chrisj.DisplayName, - MockIdentityManagementService.Chrisjoh.DisplayName, - MockIdentityManagementService.Chrisjohn.DisplayName, - MockIdentityManagementService.Chrisjohns.DisplayName - }; - - var sourceWorkItems = generator - .Items - // Run post-randomization to enable our scenario - .Select( - s => - { - var i = Randomizer.Instance.Next(0, assignees.Length - 1); - s[MockIdentityType.BackingField] = assignees[i]; - - return s; - }); - - _workItemMappings = sourceWorkItems.Select(t => new KeyValuePair(t, new MockIdentityType())).ToList(); + // Intentionally left blank } - [Benchmark] - public IEnumerable> Execute() + [Config(typeof(BenchmarkConfig))] + public class Benchmark { - _strategy.Map(typeof(MockIdentityType), _workItemMappings, null); - return _workItemMappings; + private IWorkItemMapperStrategy _strategy; + private IEnumerable> _workItemMappings; + + [Setup] + public void SetupData() + { + var propertyInspector = new PropertyInspector(new PropertyReflector()); + _strategy = new BulkIdentityAwareAttributeMapperStrategy( + propertyInspector, + new MockIdentityManagementService() + ); + + var wis = new MockWorkItemStore(); + var generator = new WorkItemGenerator(() => wis.Create(), new[] { "Revisions", "Item" }); + wis.Add(generator.Generate()); + + _workItemMappings = generator.Items.Select(t => new KeyValuePair(t, new MockIdentityType())).ToList(); + + } + + [Benchmark] + public IEnumerable> Execute() + { + _strategy.Map(typeof(MockIdentityType), _workItemMappings, null); + return _workItemMappings; + } } + } - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("Performance")] - [TestCategory("Benchmark")] - public void Execute_Identity_Mapping_Performance_Benchmark() + +} + +namespace Microsoft.Qwiq.Mapper.Tests +{ + [TestClass] + public class Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy : ContextSpecification + { + private B.Benchmark _benchmark; + + public override void Given() { - BenchmarkRunner.Run(); + _benchmark = new B.Benchmark(); + _benchmark.SetupData(); + } + + public override void When() + { + _benchmark.Execute(); } [TestMethod] - [TestCategory("localOnly")] - [TestCategory("Performance")] - public void Execute_Identity_Mapping() + public void Execute() { - Execute(); + Assert.Inconclusive("There is no condition verified. This executes to ensure the benchmark code functions without exception."); } } } diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index 4e8645da..cc3d2bbe 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -150,6 +150,10 @@ {DB07E690-4B77-414F-91C7-1A48C9F01F24} Qwiq.Mocks + + {B45C92B0-AC36-409D-86A5-5428C87384C3} + Qwiq.Tests.Common + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs b/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs new file mode 100644 index 00000000..1feaaec3 --- /dev/null +++ b/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Qwiq.Mapper.Attributes; + +using Newtonsoft.Json; + +namespace Microsoft.Qwiq.Mapper.Benchmark.Tests +{ + [WorkItemType("Task")] + public class MockModel : IIdentifiable + { + public const string ForwardLinkName = "NS.SampleLink-Forward"; + + public const string ReverseLinkName = "NS.SampleLink-Reverse"; + + private string _assignedTo; + + private string _closedBy; + + private IEnumerable _givers; + + private string _history; + + private string _issueType; + + private string _keywords; + + private string _milestone; + + private string _openedBy; + + private string _product; + + private string _productFamily; + + private string _release; + + private string _releaseType; + + private string _resolution; + + private string _status; + + private string _tags; + + private IEnumerable _takers; + + private string _title; + + private string _treePath; + + /// + /// The alias of the user to which this issue is assigned. + /// + [FieldDefinition("Assigned To")] + public virtual string AssignedTo + { + get => _assignedTo ?? string.Empty; + set => _assignedTo = value; + } + + /// + /// Used for "As Of" queries. For each revision of an issue, the ChangedDate is when the issue was modified + /// (or opened for the first revision), and that bug is considered current until . + /// If the revision is the lastest revision, the revised date is 9999/01/01 to avoid NULL values. + /// + [FieldDefinition("Changed Date")] + public virtual DateTime ChangedDate { get; set; } + + /// + /// The alias of the user that closed this issue. + /// + [FieldDefinition("Closed By")] + public virtual string ClosedBy + { + get => _closedBy ?? string.Empty; + set => _closedBy = value; + } + + /// + /// The DateTime when this issue was closed in the local timezone. Null if the issue is still open. + /// + [FieldDefinition("Closed Date")] + public virtual DateTime? ClosedDate { get; set; } + + [FieldDefinition("Custom String 01", true)] + public double Effort { get; set; } + + [WorkItemLink(typeof(SubMockModel), ReverseLinkName)] + public IEnumerable Givers + { + get => _givers ?? Enumerable.Empty(); + internal set => _givers = value; + } + + [FieldDefinition("History")] + public virtual string History + { + get => _history ?? string.Empty; + set => _history = value; + } + + [JsonIgnore] + public int? Id + { + get => ID; + set => ID = value.GetValueOrDefault(); + } + + [FieldDefinition("Id")] + public virtual int ID { get; set; } + + /// + /// Type of issue this object represents (e.g. "Code Bug", "Dev Task", "Spec Bug", "Buffer", etc.) + /// + [FieldDefinition("Issue Type")] + public virtual string IssueType + { + get => _issueType ?? string.Empty; + set => _issueType = value; + } + + /// + /// The issue's keywords text field. If there are no keywords it returns an empty string. + /// + [FieldDefinition("Keywords")] + public virtual string KeyWords + { + get => _keywords ?? string.Empty; + set => _keywords = value; + } + + /// + /// The string of this issue's milestone (e.g. 'M1 Coding', 'M1', etc.). + /// + [FieldDefinition("Iteration Path")] + public virtual string Milestone + { + get => _milestone ?? string.Empty; + set => _milestone = value; + } + + /// + /// The user that opened this issue. + /// + [FieldDefinition("Created By")] + public virtual string OpenedBy + { + get => _openedBy ?? string.Empty; + set => _openedBy = value; + } + + /// + /// The DateTime when this issue was opened in the local timezone. + /// + [FieldDefinition("Created Date")] + public virtual DateTime OpenedDate { get; set; } + + /// + /// The priority of the issue (e.g. 1, 2, etc.). + /// + [FieldDefinition("Priority")] + public virtual int? Priority { get; set; } + + [FieldDefinition("Product")] + public virtual string Product + { + get => _product ?? string.Empty; + set => _product = value; + } + + [FieldDefinition("Product Family")] + public virtual string ProductFamily + { + get => _productFamily ?? string.Empty; + set => _productFamily = value; + } + + [FieldDefinition("Release")] + public virtual string Release + { + get => _release ?? string.Empty; + set => _release = value; + } + + [FieldDefinition("Release Type")] + public virtual string ReleaseType + { + get => _releaseType ?? string.Empty; + set => _releaseType = value; + } + + /// + /// The string of this issue's status (e.g. 'By Design', 'Submitted', 'Won't Fix', etc.). + /// + [FieldDefinition("Resolved Reason")] + public virtual string Resolution + { + get => _resolution ?? string.Empty; + set => _resolution = value; + } + + /// + /// The DateTime when this issue was last modified. + /// + [FieldDefinition("Revised Date")] + public virtual DateTime RevisedDate { get; set; } + + /// + /// The string of this issue's status (e.g. 'Active', 'Closed - Completed', etc.). + /// + [FieldDefinition("State")] + public virtual string Status + { + get => _status ?? string.Empty; + set => _status = value; + } + + [FieldDefinition("Tags")] + public virtual string Tags + { + get => _tags ?? string.Empty; + set => _tags = value; + } + + [WorkItemLink(typeof(MockModel), ForwardLinkName)] + public IEnumerable Takers + { + get => _takers ?? Enumerable.Empty(); + internal set => _takers = value; + } + + /// + /// The title of the issue. + /// + [FieldDefinition("Title")] + public virtual string Title + { + get => _title ?? string.Empty; + set => _title = value; + } + + /// + /// The 'tree path' or 'area path' of the issue (e.g. \IE-Internet Explorer\COMP-Composition and Rendering\). + /// + [FieldDefinition("Area Path")] + public virtual string TreePath + { + get => _treePath ?? string.Empty; + set => _treePath = value; + } + + [FieldDefinition("Work Item Type")] + public virtual string WorkItemType { get; set; } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs b/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs deleted file mode 100644 index 5eaa4631..00000000 --- a/test/Qwiq.Mapper.Benchmark.Tests/PerformanceTests.cs +++ /dev/null @@ -1,556 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Microsoft.Qwiq.Mapper.Attributes; -using Microsoft.Qwiq.Mocks; -using Microsoft.Qwiq.Tests.Common; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Newtonsoft.Json; - -using Qwiq.Benchmark; - -namespace Microsoft.Qwiq.Mapper.Tests -{ - // POCO serialization pulling static typed objects - // When running benchmarks, be sure to compile in Release and not attach a debugger - - - [WorkItemType("Baz")] - public class MockModel : IIdentifiable - { - private string _milestone; - - private string _title; - - private string _assignedTo; - - private string _status; - - private string _resolution; - - private string _keywords; - - private string _openedBy; - - private string _treePath; - - private string _closedBy; - - private string _tags; - - private string _history; - - private string _issueType; - - private string _productFamily; - - private string _product; - - private string _release; - - private string _releaseType; - - public const string ReverseLinkName = "NS.SampleLink-Reverse"; - public const string ForwardLinkName = "NS.SampleLink-Forward"; - - private IEnumerable _givers; - private IEnumerable _takers; - - [WorkItemLink(typeof(SubMockModel), ReverseLinkName)] - public IEnumerable Givers - { - get { return (_givers ?? Enumerable.Empty()); } - internal set { _givers = value; } - } - - [WorkItemLink(typeof(MockModel), ForwardLinkName)] - public IEnumerable Takers - { - get { return (_takers ?? Enumerable.Empty()); } - internal set { _takers = value; } - } - - [JsonIgnore] - public int? Id - { - get - { - return ID; - } - set - { - ID = value.GetValueOrDefault(); - } - } - - [FieldDefinition("Id")] - public virtual int ID { get; set; } - - /// - /// The title of the issue. - /// - [FieldDefinition("Title")] - public virtual string Title - { - get - { - return _title ?? string.Empty; - } - set - { - _title = value; - } - } - - /// - /// The priority of the issue (e.g. 1, 2, etc.). - /// - [FieldDefinition("Priority")] - public virtual int? Priority { get; set; } - - /// - /// The alias of the user to which this issue is assigned. - /// - [FieldDefinition("Assigned To")] - - public virtual string AssignedTo - { - get - { - return _assignedTo ?? string.Empty; - } - set - { - _assignedTo = value; - } - } - - /// - /// The string of this issue's status (e.g. 'Active', 'Closed - Completed', etc.). - /// - [FieldDefinition("State")] - public virtual string Status - { - get - { - return _status ?? string.Empty; - } - set - { - _status = value; - } - } - - /// - /// The string of this issue's status (e.g. 'By Design', 'Submitted', 'Won't Fix', etc.). - /// - [FieldDefinition("Resolved Reason")] - public virtual string Resolution - { - get - { - return _resolution ?? string.Empty; - } - set - { - _resolution = value; - } - } - - /// - /// The string of this issue's milestone (e.g. 'M1 Coding', 'M1', etc.). - /// - [FieldDefinition("Iteration Path")] - public virtual string Milestone - { - get - { - return _milestone ?? string.Empty; - } - set - { - _milestone = value; - } - } - - /// - /// The DateTime when this issue was opened in the local timezone. - /// - [FieldDefinition("Created Date")] - public virtual DateTime OpenedDate { get; set; } - - /// - /// The user that opened this issue. - /// - [FieldDefinition("Created By")] - - public virtual string OpenedBy - { - get - { - return _openedBy ?? string.Empty; - } - set - { - _openedBy = value; - } - } - - /// - /// The issue's keywords text field. If there are no keywords it returns an empty string. - /// - [FieldDefinition("Keywords")] - public virtual string KeyWords - { - get - { - return _keywords ?? string.Empty; - } - set - { - _keywords = value; - } - } - - /// - /// Used for "As Of" queries. For each revision of an issue, the ChangedDate is when the issue was modified - /// (or opened for the first revision), and that bug is considered current until . - /// If the revision is the lastest revision, the revised date is 9999/01/01 to avoid NULL values. - /// - [FieldDefinition("Changed Date")] - public virtual DateTime ChangedDate { get; set; } - - /// - /// The DateTime when this issue was last modified. - /// - [FieldDefinition("Revised Date")] - public virtual DateTime RevisedDate { get; set; } - - /// - /// The 'tree path' or 'area path' of the issue (e.g. \IE-Internet Explorer\COMP-Composition and Rendering\). - /// - [FieldDefinition("Area Path")] - public virtual string TreePath - { - get - { - return _treePath ?? string.Empty; - } - set - { - _treePath = value; - } - } - - /// - /// Type of issue this object represents (e.g. "Code Bug", "Dev Task", "Spec Bug", "Buffer", etc.) - /// - [FieldDefinition("Issue Type")] - public virtual string IssueType - { - get - { - return _issueType ?? string.Empty; - } - set - { - _issueType = value; - } - } - - [FieldDefinition("Work Item Type")] - public virtual string WorkItemType { get; set; } - - /// - /// The alias of the user that closed this issue. - /// - [FieldDefinition("Closed By")] - public virtual string ClosedBy - { - get - { - return _closedBy ?? string.Empty; - } - set - { - _closedBy = value; - } - } - - /// - /// The DateTime when this issue was closed in the local timezone. Null if the issue is still open. - /// - [FieldDefinition("Closed Date")] - public virtual DateTime? ClosedDate { get; set; } - - [FieldDefinition("Product Family")] - public virtual string ProductFamily - { - get - { - return _productFamily ?? string.Empty; - } - set - { - _productFamily = value; - } - } - - [FieldDefinition("Product")] - public virtual string Product - { - get - { - return _product ?? string.Empty; - } - set - { - _product = value; - } - } - - [FieldDefinition("Release")] - public virtual string Release - { - get - { - return _release ?? string.Empty; - } - set - { - _release = value; - } - } - - [FieldDefinition("Tags")] - public virtual string Tags - { - get - { - return _tags ?? string.Empty; - } - set - { - _tags = value; - } - } - - [FieldDefinition("Release Type")] - public virtual string ReleaseType - { - get - { - return _releaseType ?? string.Empty; - } - set - { - _releaseType = value; - } - } - - [FieldDefinition("History")] - public virtual string History - { - get - { - return _history ?? string.Empty; - } - set - { - _history = value; - } - } - - [FieldDefinition("Custom String 01", true)] - public double Effort { get; set; } - } - - - [WorkItemType("Baz")] - public class SubMockModel : MockModel - { - } - - [TestClass] - public class BenchmarkRunnerMappingContext : ContextSpecification - { - public override void When() - { - BenchmarkRunner.Run(); - } - - - [TestMethod] - [TestCategory("Performance")] - [TestCategory("localOnly")] - public void Execute_Mapping_Performance_Benchmark() - { - // Intentionally left blank - } - - [Config(typeof(BenchmarkConfig))] - public class Benchmark - { - private WorkItemMapper _mapper; - private IEnumerable _items; - - [Setup] - public void SetupData() - { - var propertyInspector = new PropertyInspector(new PropertyReflector()); - var typeParser = TypeParser.Default; - var mappingStrategies = new IWorkItemMapperStrategy[] - { new AttributeMapperStrategy(propertyInspector, typeParser) }; - _mapper = new WorkItemMapper(mappingStrategies); - - var generator = new WorkItemGenerator(() => new MockWorkItem("Baz"), new[] { "Revisions", "Item" }); - _items = generator.Generate(); - } - - [Benchmark] - public IList Execute() - { - return _mapper.Create(_items).ToList(); - } - - - } - } - - [TestClass] - public class MapperContext : BenchmarkRunnerMappingContext - { - private Benchmark _benchmark; - - public override void Given() - { - _benchmark = new Benchmark(); - _benchmark.SetupData(); - } - - public override void When() - { - _benchmark.Execute(); - } - } - - [TestClass] - public class BenchmarkRunnerLinksMappingContext : ContextSpecification - { - public override void When() - { - BenchmarkRunner.Run(); - } - - [TestMethod] - [TestCategory("Performance")] - [TestCategory("localOnly")] - [TestCategory("Benchmark")] - public void Execute_Links_Performance_Benchmark() - { - // Intentionally left blank - } - - [Config(typeof(BenchmarkConfig))] - public class Benchmark - { - public WorkItemMapper Mapper { get; private set; } - - private IEnumerable _items; - - public IEnumerable MappingItems { get; private set; } - - [Setup] - public void SetupData() - { - var generator = new WorkItemLinkGenerator(() => new MockWorkItem("Baz"), new[] { "Revisions", "Item" }); - _items = generator.Generate(); - var propertyInspector = new PropertyInspector(new PropertyReflector()); - var typeParser = TypeParser.Default; - var mappingStrategies = new IWorkItemMapperStrategy[] - { - new AttributeMapperStrategy(propertyInspector, typeParser), - new WorkItemLinksMapperStrategy(propertyInspector, new MockWorkItemStore(_items)), - }; - Mapper = new WorkItemMapper(mappingStrategies); - MappingItems = _items.Take(500).ToList(); - } - - [Benchmark] - public IList Execute() - { - return Mapper.Create(MappingItems).ToList(); - } - - private class WorkItemLinkGenerator : WorkItemGenerator - { - private readonly MockWorkItemLinkType _linkType; - - public WorkItemLinkGenerator(Func create, IEnumerable propertiesToSkip) - : base(create, propertiesToSkip) - { - _linkType = new MockWorkItemLinkType(CoreLinkTypeReferenceNames.Hierarchy); - } - - protected override object GetRandomValue(Type propertyType) - { - switch (propertyType.ToString()) - { - case "System.Collections.Generic.ICollection`1[Microsoft.Qwiq.ILink]": - var retval = new List(); - - if (Randomizer.ShouldEnter()) - { - for (var i = 0; i < Randomizer.Instance.Next(0, 10); i++) - { - retval.Add( - new MockRelatedLink(0, Randomizer.Instance.Next(1, 36), _linkType.ForwardEnd) - ); - } - } - - if (Randomizer.ShouldEnter()) - { - for (var i = 0; i < Randomizer.Instance.Next(0, 10); i++) - { - retval.Add( - new MockRelatedLink(0, Randomizer.Instance.Next(1, 36), _linkType.ReverseEnd) - ); - } - } - - return retval; - - - default: - return base.GetRandomValue(propertyType); - } - - } - } - } - } - - [TestClass] - public class LinkMapperContext : BenchmarkRunnerLinksMappingContext - { - private Benchmark _benchmark; - - public override void Given() - { - _benchmark = new Benchmark(); - _benchmark.SetupData(); - } - - public override void When() - { - _benchmark.Mapper.Create(_benchmark.MappingItems).ToList(); - } - } -} - diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs new file mode 100644 index 00000000..f27335de --- /dev/null +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; + +using Microsoft.Qwiq.Mapper.Attributes; +using Microsoft.Qwiq.Mocks; +using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Qwiq.Benchmark; + +using B = Microsoft.Qwiq.Mapper.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy; + +namespace Microsoft.Qwiq.Mapper.Benchmark.Tests +{ + [TestClass] + public class BENCHMARK_Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy : BenchmarkContextSpecification + { + public override void When() + { + BenchmarkRunner.Run(); + } + + + [TestMethod] + [TestCategory(Constants.TestCategory.Benchmark)] + [TestCategory(Constants.TestCategory.Performance)] + [TestCategory("localOnly")] + public void Execute_Mapping_Performance_Benchmark() + { + // Intentionally left blank + } + + [Config(typeof(BenchmarkConfig))] + public class Benchmark + { + private WorkItemMapper _mapper; + private IEnumerable _items; + + [Setup] + public void SetupData() + { + var propertyInspector = new PropertyInspector(new PropertyReflector()); + var typeParser = TypeParser.Default; + var mappingStrategies = new IWorkItemMapperStrategy[] + { new AttributeMapperStrategy(propertyInspector, typeParser) }; + _mapper = new WorkItemMapper(mappingStrategies); + + var wis = new MockWorkItemStore(); + var generator = new WorkItemGenerator(() => wis.Create(), new[] { "Revisions", "Item" }); + _items = generator.Generate(); + wis.Add(_items); + } + + [Benchmark] + public IList Execute() + { + return _mapper.Create(_items).ToList(); + } + } + } +} + +namespace Microsoft.Qwiq.Mapper.Tests +{ + [TestClass] + public class Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy : ContextSpecification + { + private B.Benchmark _benchmark; + + public override void Given() + { + _benchmark = new B.Benchmark(); + _benchmark.SetupData(); + } + + public override void When() + { + _benchmark.Execute(); + } + + [TestMethod] + public void Execute() + { + Assert.Inconclusive("There is no condition verified. This executes to ensure the benchmark code functions without exception."); + } + } +} diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs new file mode 100644 index 00000000..bf1ae36a --- /dev/null +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs @@ -0,0 +1,99 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; + +using Microsoft.Qwiq.Mapper.Attributes; +using Microsoft.Qwiq.Mocks; +using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Qwiq.Benchmark; + +using B = Microsoft.Qwiq.Mapper.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_Links_with_an_AttributeMapperStrategy_and_WorkItemLinksMapperStrategy; + +namespace Microsoft.Qwiq.Mapper.Benchmark.Tests +{ + [TestClass] + public class BENCHMARK_Given_a_set_of_WorkItems_with_Links_with_an_AttributeMapperStrategy_and_WorkItemLinksMapperStrategy : BenchmarkContextSpecification + { + public override void When() + { + BenchmarkRunner.Run(); + } + + [TestMethod] + [TestCategory(Constants.TestCategory.Benchmark)] + [TestCategory(Constants.TestCategory.Performance)] + [TestCategory("localOnly")] + public void Execute_Mapping_with_Links_Performance_Benchmark() + { + // Intentionally left blank + } + + [Config(typeof(BenchmarkConfig))] + public class Benchmark + { + private WorkItemMapper _mapper; + private IEnumerable _items; + + [Setup] + public void SetupData() + { + var wis = new MockWorkItemStore(); + var generator = new WorkItemLinkGenerator( + () => wis.Create(), + wis.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Hierarchy], + (e, s, t) => new MockRelatedLink(e, s, t), + new[] { "Revisions", "Item" }); + wis.Add(generator.Generate()); + var propertyInspector = new PropertyInspector(new PropertyReflector()); + var typeParser = TypeParser.Default; + var mappingStrategies = new IWorkItemMapperStrategy[] + { + new AttributeMapperStrategy(propertyInspector, typeParser), + new WorkItemLinksMapperStrategy(propertyInspector, wis), + }; + _mapper = new WorkItemMapper(mappingStrategies); + + // Try to map 10% of what came back + var mapCount = (int)(generator.Items.Count * 0.1); + _items = generator.Items.Take(mapCount).ToList(); + } + + [Benchmark] + public IList Execute() + { + return _mapper.Create(_items).ToList(); + } + } + } +} + +namespace Microsoft.Qwiq.Mapper.Tests +{ + [TestClass] + public class Given_a_set_of_WorkItems_with_Links_with_an_AttributeMapperStrategy_and_WorkItemLinksMapperStrategy : ContextSpecification + { + private B.Benchmark _benchmark; + + public override void Given() + { + _benchmark = new B.Benchmark(); + _benchmark.SetupData(); + } + + public override void When() + { + _benchmark.Execute(); + } + + [TestMethod] + public void Execute() + { + Assert.Inconclusive("There is no condition verified. This executes to ensure the benchmark code functions without exception."); + } + } +} diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index b76a8b15..83d0de21 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -268,8 +268,11 @@ - + + + + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/SubMockModel.cs b/test/Qwiq.Mapper.Benchmark.Tests/SubMockModel.cs new file mode 100644 index 00000000..24d45615 --- /dev/null +++ b/test/Qwiq.Mapper.Benchmark.Tests/SubMockModel.cs @@ -0,0 +1,9 @@ +using Microsoft.Qwiq.Mapper.Attributes; + +namespace Microsoft.Qwiq.Mapper.Benchmark.Tests +{ + [WorkItemType("Task")] + public class SubMockModel : MockModel + { + } +} \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 11641f7e..5de469cc 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -153,7 +153,7 @@ public override void Given() new MockWorkItem(wit) {Id = 233 }, new MockWorkItem(wit) {Id = 144 } } - ).Add(related); + ).WithLinkType(related); diff --git a/test/Qwiq.Mocks/Extensions.cs b/test/Qwiq.Mocks/Extensions.cs index 87680d3f..bfca699f 100644 --- a/test/Qwiq.Mocks/Extensions.cs +++ b/test/Qwiq.Mocks/Extensions.cs @@ -6,17 +6,12 @@ namespace Microsoft.Qwiq.Mocks { public static class Extensions { - public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable workItems) + internal static void BatchSave(this MockWorkItemStore store, params IWorkItem[] workItems) { - return store.Add(workItems, null); + store.BatchSave(workItems); } - public static MockWorkItemStore Add(this MockWorkItemStore store, params IWorkItem[] workItems) - { - return Add(store, workItems as IEnumerable); - } - - public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable> values) + public static MockWorkItem Create(this MockWorkItemStore store, IEnumerable> values = null) { var project = store.Projects[0]; var wit = project.WorkItemTypes[0]; @@ -25,10 +20,26 @@ public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable(CoreFieldRefNames.WorkItemType, wit.Name); var a = new[] { tp, wp }; - values = values == null ? a : values.Union(a); + values = values?.Union(a) ?? a; var wi = wit.NewWorkItem(values); store.BatchSave(wi); + return (MockWorkItem)wi; + } + + public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable workItems) + { + return store.Add(workItems, null); + } + + public static MockWorkItemStore Add(this MockWorkItemStore store, params IWorkItem[] workItems) + { + return Add(store, workItems as IEnumerable); + } + + public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable> values) + { + Create(store, values); return store; } @@ -74,7 +85,7 @@ public static MockWorkItemStore Add( return store; } - public static MockWorkItemStore Add(this MockWorkItemStore store, params IWorkItemLinkType[] linkTypes) + public static MockWorkItemStore WithLinkType(this MockWorkItemStore store, params IWorkItemLinkType[] linkTypes) { store.WorkItemLinkTypes = new WorkItemLinkTypeCollection(store.WorkItemLinkTypes.Union(linkTypes)); return store; diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 04980265..e26c5307 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -120,21 +120,28 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision public IRegisteredLinkTypeCollection RegisteredLinkTypes { get; } - internal void BatchSave(params IWorkItem[] workItems) - { - BatchSave(workItems as IEnumerable); - } + internal void BatchSave(IEnumerable workItems) { - var missingWits = new Dictionary>(); + + + // First: Fix up the work items and save them to our dictionary foreach (var item in workItems) { - Save(item); } + // Second: Update the links for the work items + // We need to save first so we can create recipricol links if required (e.g. parent -> child also needs a child -> parent) + foreach (var item in workItems) + { + SaveLinks(item); + } + + // Third: If any of the work items have types that are missing from their project, add those + var missingWits = new Dictionary>(); foreach (var item in workItems) { var projectName = item[CoreFieldRefNames.TeamProject].ToString(); @@ -153,6 +160,7 @@ internal void BatchSave(IEnumerable workItems) } } + // Fourth: If there are any missing wits update the project and reset the project collection if (!missingWits.Any()) return; var changesRequired = false; @@ -200,64 +208,18 @@ private void Save(IWorkItem item) var id = item.Id; - // Remove any existing links for this item - //foreach (var link in _links.ToArray()) if (link.SourceId == id) _links.Remove(link); - var l = new Dictionary { { BaseLinkType.RelatedLink, 0 }, { BaseLinkType.ExternalLink, 0 }, { BaseLinkType.Hyperlink, 0 } }; - - // If there are new links add them back + if (item.Links != null && item.Links.Any()) { foreach (var link in item.Links) { l[link.BaseType]++; - - // We only support related links at the moment - if (link.BaseType != BaseLinkType.RelatedLink) continue; - var rl = link as IRelatedLink; - if (rl == null) continue; - - var mrl = rl as MockRelatedLink; - if (mrl != null) - { - var li = mrl.LinkInfo; - if (LinkInfo.Contains(li, WorkItemLinkInfoComparer.Instance)) - { - Trace.TraceWarning( - $"Warning: Duplicate link. (Type: {li.LinkType?.ImmutableName ?? "NULL"}; Source: {li.SourceId}; Target: {li.TargetId})"); - } - else - { - LinkInfo.Add(li); - } - - if (rl.LinkTypeEnd == null) continue; - - // Check to see if a recipricol link is required - if (rl.LinkTypeEnd.LinkType.IsDirectional) - { - var t = _lookup[rl.RelatedWorkItemId]; - var e = rl.LinkTypeEnd.OppositeEnd; - - // Check to see if an existing link exists - if (!t.Links.OfType().Any(p => p.RelatedWorkItemId == id && Equals(p.LinkTypeEnd, e))) - { - // There is not--create one - var tl = t.CreateRelatedLink(id, rl.LinkTypeEnd.OppositeEnd); - t.Links.Add(tl); - Save(t); - } - } - } - else - { - throw new NotSupportedException(); - } } } @@ -275,6 +237,72 @@ private void Save(IWorkItem item) _lookup[id] = item; } + private void SaveLinks(IWorkItem item) + { + var id = item.Id; + // If there are new links add them back + if (item.Links != null && item.Links.Any()) + { + foreach (var link in item.Links) + { + SaveLink(link, id); + } + } + } + + private void SaveLink(ILink link, int id) + { + // We only support related links at the moment + if (link.BaseType != BaseLinkType.RelatedLink) return; + var rl = link as IRelatedLink; + if (rl == null) return; + + var mrl = rl as MockRelatedLink; + if (mrl != null) + { + var li = mrl.LinkInfo; + if (LinkInfo.Contains(li, WorkItemLinkInfoComparer.Instance)) + { + Trace.TraceWarning( + $"Warning: Duplicate link. (Type: {li.LinkType?.ImmutableName ?? "NULL"}; Source: {li.SourceId}; Target: {li.TargetId})"); + } + else + { + LinkInfo.Add(li); + } + + if (rl.LinkTypeEnd == null) return; + + // Check to see if a recipricol link is required + if (rl.LinkTypeEnd.LinkType.IsDirectional) + { + try + { + var t = _lookup[rl.RelatedWorkItemId]; + var e = rl.LinkTypeEnd.OppositeEnd; + + // Check to see if an existing link exists + if (!t.Links.OfType().Any(p => p.RelatedWorkItemId == id && Equals(p.LinkTypeEnd, e))) + { + // There is not--create one + var tl = t.CreateRelatedLink(id, rl.LinkTypeEnd.OppositeEnd); + t.Links.Add(tl); + Save(t); + } + } + catch (KeyNotFoundException) + { + Trace.TraceWarning($"Work item {id} contains a {rl.LinkTypeEnd} to an item that does not exist: {rl.RelatedWorkItemId}."); + + } + } + } + else + { + throw new NotSupportedException(); + } + } + protected void Dispose(bool disposing) { if (disposing) From da02e8923843400f7dc68cdd097b23674be37e75 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 18 Apr 2017 17:01:22 -0700 Subject: [PATCH 100/251] Upgrade BenchmarkDotNet --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 1 + test/Qwiq.Benchmark/BenchmarkConfig.cs | 3 ++- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 24 ++++++++----------- test/Qwiq.Benchmark/app.config | 4 ++++ test/Qwiq.Benchmark/packages.config | 10 ++++---- .../Qwiq.Identity.Benchmark.Tests.csproj | 16 ++++++------- test/Qwiq.Identity.Benchmark.Tests/app.config | 4 ++++ .../packages.config | 8 +++---- .../Qwiq.Mapper.Benchmark.Tests.csproj | 16 ++++++------- test/Qwiq.Mapper.Benchmark.Tests/app.config | 4 ++++ .../packages.config | 8 +++---- 11 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 76e52df6..3752ee00 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -69,6 +69,7 @@ ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll + False ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll diff --git a/test/Qwiq.Benchmark/BenchmarkConfig.cs b/test/Qwiq.Benchmark/BenchmarkConfig.cs index ae936ae8..fc0d4e5d 100644 --- a/test/Qwiq.Benchmark/BenchmarkConfig.cs +++ b/test/Qwiq.Benchmark/BenchmarkConfig.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnostics.Windows; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; namespace Qwiq.Benchmark @@ -15,7 +16,7 @@ public BenchmarkConfig() Add(Job.Clr.With(Jit.RyuJit).With(Platform.X86).With(new GcMode { Server = true })); Add(Job.Clr.With(Jit.LegacyJit).With(Platform.X86).With(new GcMode { Server = true })); - Add(new MemoryDiagnoser()); + Add(new BenchmarkDotNet.Diagnosers.MemoryDiagnoser()); Add(new InliningDiagnoser()); Add(StatisticColumn.AllStatistics); diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index e7d761bf..34a67503 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -39,21 +39,17 @@ 4 - - ..\..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll - True + + ..\..\packages\BenchmarkDotNet.0.10.3\lib\net45\BenchmarkDotNet.dll - - ..\..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll - True + + ..\..\packages\BenchmarkDotNet.Core.0.10.3\lib\net45\BenchmarkDotNet.Core.dll - - ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.9\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll - True + + ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.3\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll - True + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll @@ -129,8 +125,8 @@ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll True - - ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll diff --git a/test/Qwiq.Benchmark/app.config b/test/Qwiq.Benchmark/app.config index 0ef7bcfb..4a32a14b 100644 --- a/test/Qwiq.Benchmark/app.config +++ b/test/Qwiq.Benchmark/app.config @@ -6,6 +6,10 @@ + + + + diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index 64070f1c..2e37128d 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -1,9 +1,9 @@  - - - - + + + + @@ -45,7 +45,7 @@ - + diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index cc3d2bbe..0814f409 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -34,14 +34,14 @@ 4 - - ..\..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll + + ..\..\packages\BenchmarkDotNet.0.10.3\lib\net45\BenchmarkDotNet.dll - - ..\..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll + + ..\..\packages\BenchmarkDotNet.Core.0.10.3\lib\net45\BenchmarkDotNet.Core.dll - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll @@ -103,8 +103,8 @@ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll - - ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll diff --git a/test/Qwiq.Identity.Benchmark.Tests/app.config b/test/Qwiq.Identity.Benchmark.Tests/app.config index dbb474ee..112abb2a 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/app.config +++ b/test/Qwiq.Identity.Benchmark.Tests/app.config @@ -2,6 +2,10 @@ + + + + diff --git a/test/Qwiq.Identity.Benchmark.Tests/packages.config b/test/Qwiq.Identity.Benchmark.Tests/packages.config index 73966295..ae4bbe0e 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/packages.config +++ b/test/Qwiq.Identity.Benchmark.Tests/packages.config @@ -1,8 +1,8 @@  - - - + + + @@ -42,7 +42,7 @@ - + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index 83d0de21..8cba140e 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -34,14 +34,14 @@ 4 - - ..\..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll + + ..\..\packages\BenchmarkDotNet.0.10.3\lib\net45\BenchmarkDotNet.dll - - ..\..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll + + ..\..\packages\BenchmarkDotNet.Core.0.10.3\lib\net45\BenchmarkDotNet.Core.dll - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll @@ -246,8 +246,8 @@ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll True - - ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll diff --git a/test/Qwiq.Mapper.Benchmark.Tests/app.config b/test/Qwiq.Mapper.Benchmark.Tests/app.config index 3e276570..0ac73ec8 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/app.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/app.config @@ -22,6 +22,10 @@ + + + + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index a5168d38..c74dda62 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -1,8 +1,8 @@  - - - + + + @@ -53,7 +53,7 @@ - + From c0644046870d42e84fd19c03da6a6efd313212f9 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 18 Apr 2017 17:34:34 -0700 Subject: [PATCH 101/251] Add TFS Extended client to integration tests --- .../Qwiq.Integration.Tests.csproj | 116 ++++++++++++++++++ test/Qwiq.Integration.Tests/packages.config | 5 + 2 files changed, 121 insertions(+) diff --git a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj index e9fcf557..21077f3f 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj @@ -47,9 +47,116 @@ ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll + + + ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll + True + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll + True + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll + ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll @@ -82,6 +189,13 @@ + + ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + @@ -148,7 +262,9 @@ + + \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/packages.config b/test/Qwiq.Integration.Tests/packages.config index 64996081..3cfbc88b 100644 --- a/test/Qwiq.Integration.Tests/packages.config +++ b/test/Qwiq.Integration.Tests/packages.config @@ -2,7 +2,12 @@ + + + + + From 04a84d9ae80496f150e23cfbff1ba0de1b535373 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 18 Apr 2017 17:38:01 -0700 Subject: [PATCH 102/251] Upgrade Castle.Core --- src/Qwiq.Core/Qwiq.Core.csproj | 5 ++--- src/Qwiq.Core/packages.config | 2 +- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 5 ++--- test/Qwiq.Core.Tests/packages.config | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index c23633f3..bb50c51a 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -36,9 +36,8 @@ 4 - - ..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll - True + + ..\..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index 045e9f52..7b04f103 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index eea2fab7..79ef19de 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -43,9 +43,8 @@ false - - ..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll - True + + ..\..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index cb40f129..d5f0cd8d 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -1,6 +1,6 @@  - + From c410a26bacc0d8db3432880359a7b2877df92203 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 18 Apr 2017 17:46:00 -0700 Subject: [PATCH 103/251] Update nuget.config - Remove additional package sources (to reduce restore time / noise) - Prevent user from disabling nuget.org and prevent restoration --- nuget.config | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nuget.config b/nuget.config index fd3b6048..f0848830 100644 --- a/nuget.config +++ b/nuget.config @@ -4,6 +4,7 @@ + @@ -18,4 +19,8 @@ - + + + + + \ No newline at end of file From 92ee0ccc7f7695a5934079fbcba007de86fd82cc Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Apr 2017 15:27:07 -0700 Subject: [PATCH 104/251] Update appveyor.yml to match config with UI values - File now includes VS2017 image - msbuild and vstest use Dev15 tools - Nuget restore explicitly uses version installed to `.tools` folder after executing `init.ps1` - Artifact publish settings configured in file using secure values from appveyor.com - `Performance` test category added to list of test exclusions - Parallel build execution disabled to avoid build double-writes issues --- appveyor.yml | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a3dd1eef..ac90f9cd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,28 +1,36 @@ +version: 1.0.{build} +image: Visual Studio 2017 configuration: Release - platform: Any CPU - -cache: - - packages -> **\packages.config - install: - - cmd: choco install gitversion.portable -pre -y - +- cmd: choco install gitversion.portable -pre -y before_build: - - ps: .\init.ps1 - - cmd: nuget restore -NonInteractive -DisableParallelProcessing - - cmd: gitversion /l console /output buildserver - +- ps: >- + .\init.ps1; + & .\.tools\nuget.exe restore -NonInteractive -DisableParallelProcessing; + gitversion /l console /output buildserver build: publish_nuget: true publish_nuget_symbols: true include_nuget_references: true - parallel: true verbosity: minimal - test: categories: except: - localOnly - Benchmark - + - Performance +artifacts: +- path: '*.nupkg' + name: NuGet +deploy: +- provider: NuGet + server: https://www.myget.org/F/qwiq/api/v2/package + api_key: + secure: 8wGYx8W+ojYQJsAJpRLez+KwwFa+OW7cvsn5RoR08WQpi5klhTq/9b/9YaEu/5Bx + symbol_server: https://www.myget.org/F/qwiq/symbols/api/v2/package +notifications: +- provider: GitHubPullRequest + on_build_success: true + on_build_failure: true + on_build_status_changed: true \ No newline at end of file From e7289cf6a37b1205b8613161626ebe3fb634260a Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Tue, 18 Apr 2017 18:48:14 -0700 Subject: [PATCH 105/251] Minimize dependencies --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 2 - src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 3 +- src/Qwiq.Core/Qwiq.Core.csproj | 10 +- src/Qwiq.Core/packages.config | 2 - src/Qwiq.Identity/Qwiq.Identity.csproj | 141 +-------------- src/Qwiq.Identity/app.config | 30 ++++ src/Qwiq.Identity/packages.config | 10 -- src/Qwiq.Linq/Qwiq.Linq.csproj | 159 +---------------- src/Qwiq.Linq/packages.config | 14 -- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 162 +----------------- src/Qwiq.Mapper/packages.config | 14 -- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 26 +-- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 20 +-- test/Qwiq.Core.Tests/app.config | 30 ++++ test/Qwiq.Core.Tests/packages.config | 2 - .../Qwiq.Identity.Benchmark.Tests.csproj | 9 +- .../Qwiq.Identity.Tests.csproj | 4 +- .../Qwiq.Integration.Tests.csproj | 16 +- test/Qwiq.Integration.Tests/app.config | 30 ++++ test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 8 +- .../Qwiq.Mapper.Benchmark.Tests.csproj | 149 +--------------- .../packages.config | 10 -- .../Qwiq.Mapper.Tests.csproj | 5 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 6 +- test/Qwiq.Mocks/packages.config | 1 - .../Qwiq.Tests.Common.csproj | 3 - test/Qwiq.Tests.Common/packages.config | 1 - 27 files changed, 125 insertions(+), 742 deletions(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 3752ee00..7a5c7fed 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -34,7 +34,6 @@ 4 - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll @@ -69,7 +68,6 @@ ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - False ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 3c33a393..837cd0ac 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -34,7 +34,6 @@ 4 - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll @@ -256,8 +255,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index bb50c51a..73c1e9f1 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -1,5 +1,5 @@  - + @@ -61,10 +61,6 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll @@ -234,14 +230,12 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - \ No newline at end of file diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index 7b04f103..f0c5618a 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -7,9 +7,7 @@ - - \ No newline at end of file diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 927d5f81..72ef6206 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -1,5 +1,5 @@  - + @@ -57,163 +57,32 @@ - + ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - - ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll - @@ -227,16 +96,12 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/Qwiq.Identity/app.config b/src/Qwiq.Identity/app.config index 556f3897..c314ab62 100644 --- a/src/Qwiq.Identity/app.config +++ b/src/Qwiq.Identity/app.config @@ -16,4 +16,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index 90e22b51..056504f8 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -3,18 +3,8 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index 9927ae12..eeb01aab 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -1,5 +1,5 @@  - + @@ -35,159 +35,8 @@ 4 - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - - ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - - - - ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll - @@ -246,16 +95,12 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index f3ebd447..afeba8ad 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -1,19 +1,5 @@  - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 58e0c818..8a9b20ba 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -1,5 +1,5 @@  - + @@ -35,164 +35,12 @@ 4 - + ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll - True - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - - - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - - ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - - - - ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll - @@ -231,16 +79,12 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index 90e22b51..c1f8b027 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -2,19 +2,5 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 34a67503..b2634357 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -1,5 +1,5 @@  - + @@ -53,15 +53,12 @@ ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll - True ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - True ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll - True ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -72,79 +69,62 @@ ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll - True ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll - True ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - True ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll - True ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll - True ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - True ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll - True ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll - True ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll - True ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll - True ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll - True ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll - True @@ -211,17 +191,17 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 79ef19de..cf7730f6 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -1,5 +1,5 @@  - + @@ -43,9 +43,6 @@ false - - ..\..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll @@ -180,16 +177,11 @@ ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll - True @@ -244,10 +236,6 @@ {8ac61b6e-bec1-482d-a043-c65d2d343b35} Qwiq.Core - - {0f70e1c2-d696-4749-8601-374a7c9c268a} - Qwiq.Core.Rest - {6f5ffc42-0539-4161-b348-a54adb57c2bd} Qwiq.Core.Soap @@ -281,17 +269,17 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - - + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/app.config b/test/Qwiq.Core.Tests/app.config index c83509f5..ab4357fc 100644 --- a/test/Qwiq.Core.Tests/app.config +++ b/test/Qwiq.Core.Tests/app.config @@ -12,4 +12,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index d5f0cd8d..ab9526b4 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -1,6 +1,5 @@  - @@ -12,7 +11,6 @@ - diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index 0814f409..b91169fb 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -49,7 +49,6 @@ ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -69,7 +68,6 @@ - ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll @@ -83,7 +81,6 @@ ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll @@ -160,11 +157,11 @@ - - + - + + diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index b1a91420..df8494b6 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -1,5 +1,5 @@  - + @@ -112,6 +112,7 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. @@ -121,6 +122,5 @@ - \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj index 21077f3f..52a58855 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj @@ -236,18 +236,6 @@ {6f5ffc42-0539-4161-b348-a54adb57c2bd} Qwiq.Core.Soap - - {1EDEB333-3084-42BD-B273-4009B4B18541} - Qwiq.Linq - - - {016E8D93-4195-4639-BCD5-77633E8E1681} - Qwiq.Mapper - - - {DB07E690-4B77-414F-91C7-1A48C9F01F24} - Qwiq.Mocks - {B45C92B0-AC36-409D-86A5-5428C87384C3} Qwiq.Tests.Common @@ -255,16 +243,16 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + - - \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/app.config b/test/Qwiq.Integration.Tests/app.config index c83509f5..ab4357fc 100644 --- a/test/Qwiq.Integration.Tests/app.config +++ b/test/Qwiq.Integration.Tests/app.config @@ -12,4 +12,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index 206eac05..a3163c23 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -1,5 +1,5 @@  - + @@ -66,9 +66,7 @@ - - Designer - + @@ -109,6 +107,7 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. @@ -118,6 +117,5 @@ - \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index 8cba140e..75eed91c 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -49,135 +49,9 @@ ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll - - - ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll - True - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll - - - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll - - - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -200,16 +74,12 @@ - ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll - - ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll @@ -217,10 +87,6 @@ ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll @@ -242,19 +108,12 @@ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll - - ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll - @@ -301,19 +160,17 @@ - - + - + + - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index c74dda62..645a9525 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -3,19 +3,10 @@ - - - - - - - - - @@ -30,7 +21,6 @@ - diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index a3dd1321..3608e367 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -1,5 +1,5 @@  - + @@ -49,7 +49,6 @@ ..\..\packages\Should.1.1.20\lib\Should.dll - True @@ -125,6 +124,7 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. @@ -134,7 +134,6 @@ - + + + + + .\TestResults + + + x86 + + + Framework45 + + + + + + + + + + + + + + + .*\.dll$ + .*\.exe$ + + + .*CPPUnitTestFramework.* + .*TestAdapter.* + .*UnitTests.* + .*xunit.* + .*threading.* + + + + + + + + ^Fabrikam\.UnitTest\..* + ^std::.* + ^ATL::.* + .*::__GetTestMethodInfo.* + ^Microsoft::VisualStudio::CppCodeCoverageFramework::.* + ^Microsoft::VisualStudio::CppUnitTestFramework::.* + + + + + + + + ^System.Diagnostics.DebuggerHiddenAttribute$ + ^System.Diagnostics.DebuggerNonUserCodeAttribute$ + ^System.Runtime.CompilerServices.CompilerGeneratedAttribute$ + ^System.CodeDom.Compiler.GeneratedCodeAttribute$ + ^System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute$ + ^NUnit.Framework.TestFixtureAttribute$ + ^Xunit.FactAttribute$ + ^Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute$ + + + + + + + .*\\atlmfc\\.* + .*\\vctools\\.* + .*\\public\\sdk\\.* + .*\\microsoft sdks\\.* + .*\\vc\\include\\.* + + + + + + + + + + + + + + + ^B77A5C561934E089$ + ^B03F5F7F11D50A3A$ + ^31BF3856AD364E35$ + ^89845DCD8080CC91$ + ^71E9BCE111E9429C$ + ^8F50407C4E9E73B6$ + ^E361AF139669C375$ + + + + + + True + True + True + False + + + + + + + + + + + + + + + + + True + false + False + False + + \ No newline at end of file From bf6e54f277716a6db2acc5a809ecf2374c822666 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Apr 2017 11:54:17 -0700 Subject: [PATCH 107/251] Enable Code Analysis rules Update non-shipping ruleset Suppress CA1014, CA1707, and CA1709 because it's too noisy for our test style. --- build/rulesets/noship.ruleset | 28 ++++ build/rulesets/ship.ruleset | 139 ++++++++++++++++++ src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 4 + src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 4 + src/Qwiq.Core/Qwiq.Core.csproj | 4 + src/Qwiq.Identity/Qwiq.Identity.csproj | 4 + src/Qwiq.Linq/Qwiq.Linq.csproj | 4 + src/Qwiq.Mapper/Qwiq.Mapper.csproj | 4 + test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 4 + test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 4 + .../Qwiq.Identity.Benchmark.Tests.csproj | 4 + .../Qwiq.Identity.Tests.csproj | 4 + .../Qwiq.Integration.Tests.csproj | 4 + test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 4 + .../Qwiq.Mapper.Benchmark.Tests.csproj | 4 + .../Qwiq.Mapper.Tests.csproj | 4 + test/Qwiq.Mocks/Qwiq.Mocks.csproj | 4 + .../Qwiq.Tests.Common.csproj | 4 + 18 files changed, 231 insertions(+) create mode 100644 build/rulesets/noship.ruleset create mode 100644 build/rulesets/ship.ruleset diff --git a/build/rulesets/noship.ruleset b/build/rulesets/noship.ruleset new file mode 100644 index 00000000..3e921e45 --- /dev/null +++ b/build/rulesets/noship.ruleset @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/rulesets/ship.ruleset b/build/rulesets/ship.ruleset new file mode 100644 index 00000000..0f358df0 --- /dev/null +++ b/build/rulesets/ship.ruleset @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 7a5c7fed..00f9a701 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -24,6 +24,8 @@ prompt 4 CS0108 + ..\..\build\rulesets\ship.ruleset + true pdbonly @@ -32,6 +34,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 837cd0ac..a130d9ee 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -24,6 +24,8 @@ prompt 4 CS0108 + ..\..\build\rulesets\ship.ruleset + true pdbonly @@ -32,6 +34,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 73c1e9f1..b9731e7f 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -26,6 +26,8 @@ prompt 4 CS0108 + ..\..\build\rulesets\ship.ruleset + true pdbonly @@ -34,6 +36,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 72ef6206..2be27fa9 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -26,6 +26,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true AnyCPU @@ -35,6 +37,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index eeb01aab..eeb84722 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -25,6 +25,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true pdbonly @@ -33,6 +35,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 8a9b20ba..f1fd5e7e 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -25,6 +25,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true pdbonly @@ -33,6 +35,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index b2634357..a5489231 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -29,6 +29,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -37,6 +39,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index cf7730f6..255d5c50 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -32,6 +32,8 @@ prompt 4 false + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -41,6 +43,8 @@ prompt 4 false + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index b91169fb..d66d0bdc 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -24,6 +24,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -32,6 +34,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index df8494b6..3ac78a93 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -30,6 +30,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -38,6 +40,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj index 52a58855..3cbc58d9 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj @@ -28,6 +28,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -36,6 +38,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index a3163c23..fbd9c4d1 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -31,6 +31,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -39,6 +41,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index 75eed91c..347e23ba 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -24,6 +24,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -32,6 +34,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 3608e367..77f5ecf3 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -31,6 +31,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -39,6 +41,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 84f52dcf..81b08ef3 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -23,6 +23,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true pdbonly @@ -31,6 +33,8 @@ TRACE prompt 4 + ..\..\build\rulesets\ship.ruleset + true diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index 50996893..2cf20d4c 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -24,6 +24,8 @@ DEBUG;TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true pdbonly @@ -32,6 +34,8 @@ TRACE prompt 4 + ..\..\build\rulesets\noship.ruleset + true From 2fe2c5c11883f6a79a62519562c0316b5943feca Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Apr 2017 15:07:50 -0700 Subject: [PATCH 108/251] Update code coverage filter --- Qwiq.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Qwiq.sln.DotSettings b/Qwiq.sln.DotSettings index 07a8c647..b4cfa9a3 100644 --- a/Qwiq.sln.DotSettings +++ b/Qwiq.sln.DotSettings @@ -1,2 +1,2 @@  - <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Integration.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Integration.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file From 500d1e946fd06c70a71acab40554d92ea212ea90 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Apr 2017 15:45:24 -0700 Subject: [PATCH 109/251] Code clean up Projects: Linq, Mapper, Core.Tests, Mocks, Core, Soap, Rest --- src/Qwiq.Core.Rest/FieldDefinition.cs | 2 +- ...n.cs => IInternalTeamProjectCollection.cs} | 2 +- src/Qwiq.Core.Rest/IdentityDescriptor.cs | 2 +- src/Qwiq.Core.Rest/LinkCollection.cs | 2 +- src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs | 5 +- src/Qwiq.Core.Rest/Query.cs | 12 +- src/Qwiq.Core.Rest/QueryFactory.cs | 12 +- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 23 +-- src/Qwiq.Core.Rest/VssConnectionAdapter.cs | 8 +- src/Qwiq.Core.Rest/WorkItemLinkType.cs | 22 --- src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs | 2 +- src/Qwiq.Core.Rest/WorkItemStore.cs | 14 +- src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 15 +- src/Qwiq.Core.Soap/FieldCollection.cs | 4 +- ...n.cs => IInternalTeamProjectCollection.cs} | 2 +- src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs | 5 +- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 22 +-- .../TfsTeamProjectCollection.cs | 10 +- src/Qwiq.Core.Soap/WorkItem.cs | 2 +- src/Qwiq.Core.Soap/WorkItemStore.cs | 14 +- src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 15 +- src/Qwiq.Core/BaseLinkType.cs | 1 + src/Qwiq.Core/ClientType.cs | 2 +- src/Qwiq.Core/Comparer.cs | 22 +++ src/Qwiq.Core/CoreField.cs | 2 +- src/Qwiq.Core/CoreFieldRefNames.cs | 6 +- .../AuthenticationFailedNotification.cs | 13 +- .../Credentials/AuthenticationOptions.cs | 103 ++++++++---- .../AuthenticationSuccessNotification.cs | 6 +- .../Credentials/AuthenticationType.cs | 15 -- .../Credentials/AuthenticationTypes.cs | 15 ++ .../Credentials/CredentialNotification.cs | 6 +- .../Credentials/CredentialsFactory.cs | 11 +- .../Credentials/CredentialsNotifications.cs | 87 +++++----- src/Qwiq.Core/Credentials/TfsCredentials.cs | 23 +-- .../Credentials/VssCredentialsComparer.cs | 51 ------ .../DeniedOrNotExistException.cs | 11 +- .../FieldDefinitionNotExistException.cs | 7 + .../Exceptions/InnerExceptionExploder.cs | 1 + .../PageSizeRangeException.cs | 7 + .../Exceptions/TransientException.cs | 13 +- .../Exceptions/VssExceptionMapper.cs | 3 +- .../WorkItemTypeDeniedOrNotExistException.cs | 10 ++ src/Qwiq.Core/FieldCollection.cs | 9 +- src/Qwiq.Core/FieldDefinition.cs | 10 +- src/Qwiq.Core/FieldDefinitionCollection.cs | 13 +- .../FieldDefinitionCollectionComparer.cs | 6 +- src/Qwiq.Core/FieldDefinitionComparer.cs | 9 +- src/Qwiq.Core/GenericComparer.cs | 23 ++- src/Qwiq.Core/GlobalSuppressions.cs | Bin 0 -> 17670 bytes src/Qwiq.Core/IFieldCollection.cs | 4 +- src/Qwiq.Core/IFieldDefinitionCollection.cs | 2 +- src/Qwiq.Core/INodeCollection.cs | 2 +- src/Qwiq.Core/IProjectCollection.cs | 2 +- src/Qwiq.Core/IProjectProperty.cs | 6 - ...ReadOnlyList.cs => IReadOnlyCollection.cs} | 2 +- ...WithId.cs => IReadOnlyCollectionWithId.cs} | 2 +- .../IRegisteredLinkTypeCollection.cs | 2 +- src/Qwiq.Core/IRelatedLink.cs | 1 + src/Qwiq.Core/ITeamFoundationIdentity.cs | 2 +- src/Qwiq.Core/ITfsTeamProjectCollection.cs | 12 +- src/Qwiq.Core/IWorkItem.Extensions.cs | 22 +++ src/Qwiq.Core/IWorkItem.cs | 4 +- src/Qwiq.Core/IWorkItemCommon.cs | 2 +- src/Qwiq.Core/IWorkItemLinkTypeCollection.cs | 2 +- .../IWorkItemLinkTypeEndCollection.cs | 2 +- src/Qwiq.Core/IWorkItemStore.Extensions.cs | 26 +++ src/Qwiq.Core/IWorkItemStore.cs | 8 +- src/Qwiq.Core/IWorkItemType.Extensions.cs | 30 ++++ src/Qwiq.Core/IWorkItemTypeCollection.cs | 2 +- src/Qwiq.Core/IdentifiableComparer.cs | 15 +- src/Qwiq.Core/IdentityDescriptor.cs | 58 ++++--- src/Qwiq.Core/IdentityDescriptorComparer.cs | 15 +- src/Qwiq.Core/IdentityFieldValue.cs | 36 ++--- src/Qwiq.Core/Node.cs | 6 +- src/Qwiq.Core/NodeCollection.cs | 8 +- src/Qwiq.Core/NodeComparer.cs | 3 +- src/Qwiq.Core/NullableIdentifiableComparer.cs | 15 +- src/Qwiq.Core/Project.cs | 10 +- src/Qwiq.Core/ProjectCollection.cs | 2 +- src/Qwiq.Core/ProjectComparer.cs | 13 +- src/Qwiq.Core/Properties/AssemblyInfo.cs | 5 +- src/Qwiq.Core/Qwiq.Core.csproj | 51 +++--- ...{ReadOnlyList.cs => ReadOnlyCollection.cs} | 16 +- ...tWithId.cs => ReadOnlyCollectionWithId.cs} | 17 +- ...cs => ReadOnlyCollectionWithIdComparer.cs} | 14 +- src/Qwiq.Core/RegisteredLinkTypeCollection.cs | 2 +- src/Qwiq.Core/RelatedLink.cs | 8 +- src/Qwiq.Core/Revision.cs | 29 ++-- src/Qwiq.Core/TeamFoundationIdentity.cs | 4 +- .../TeamFoundationIdentityComparer.cs | 7 +- src/Qwiq.Core/TypeParser.cs | 38 ++--- src/Qwiq.Core/WorkItem.cs | 8 +- src/Qwiq.Core/WorkItemCommon.cs | 4 +- src/Qwiq.Core/WorkItemComparer.cs | 6 +- src/Qwiq.Core/WorkItemCore.cs | 19 ++- src/Qwiq.Core/WorkItemExtensions.cs | 46 ------ src/Qwiq.Core/WorkItemLinkInfo.cs | 10 +- src/Qwiq.Core/WorkItemLinkInfoComparer.cs | 5 +- src/Qwiq.Core/WorkItemLinkType.cs | 38 ++++- src/Qwiq.Core/WorkItemLinkTypeCollection.cs | 4 +- src/Qwiq.Core/WorkItemLinkTypeComparer.cs | 19 ++- src/Qwiq.Core/WorkItemLinkTypeEnd.cs | 27 ++-- .../WorkItemLinkTypeEndCollection.cs | 2 +- src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs | 4 +- src/Qwiq.Core/WorkItemType.cs | 6 +- src/Qwiq.Core/WorkItemTypeCollection.cs | 8 +- .../WorkItemTypeCollectionComparer.cs | 6 +- src/Qwiq.Core/WorkItemTypeComparer.cs | 14 +- ...ulkIdentityAwareAttributeMapperStrategy.cs | 6 +- .../Mapper/IdentifiableComparer.cs | 52 ------ src/Qwiq.Identity/Qwiq.Identity.csproj | 18 +-- src/Qwiq.Linq/Qwiq.Linq.csproj | 17 +- src/Qwiq.Linq/SimpleFieldMapper.cs | 3 +- .../Attributes/AttributeMapperStrategy.cs | 2 +- .../Attributes/PropertyInspector.cs | 6 +- .../Attributes/PropertyReflector.cs | 6 +- .../Attributes/WorkItemLinksMapperStrategy.cs | 17 +- src/Qwiq.Mapper/IWorkItemMapper.cs | 5 +- src/Qwiq.Mapper/IWorkItemMapperStrategy.cs | 4 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 17 +- src/Qwiq.Mapper/WorkItemMapper.cs | 8 +- src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs | 13 +- test/Qwiq.Benchmark/BenchmarkConfig.cs | 2 +- .../BenchmarkContextSpecification.cs | 10 +- test/Qwiq.Benchmark/Constants.cs | 8 +- test/Qwiq.Benchmark/GlobalSuppressions.cs | Bin 0 -> 2182 bytes test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 62 ++------ test/Qwiq.Benchmark/TraceEvent.ReadMe.txt | 79 ---------- .../TraceEvent.ReleaseNotes.txt | 61 ------- .../_TraceEventProgrammersGuide.docx | Bin 196963 -> 0 bytes test/Qwiq.Core.Tests/GlobalSuppressions.cs | Bin 0 -> 9240 bytes test/Qwiq.Core.Tests/Mocks/MockException.cs | 11 ++ test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 26 ++- test/Qwiq.Core.Tests/TypeParserTests.cs | 2 +- .../WorkItemLinkTypeComparerTests.cs | 4 +- .../Benchmark.cs | 12 +- .../GlobalSuppressions.cs | Bin 0 -> 2380 bytes .../Qwiq.Identity.Benchmark.Tests.csproj | 22 +-- ...entityAwareAttributeMapperStrategyTests.cs | 4 +- ...entityManagementService.ExtensionsTests.cs | 16 +- .../Mocks/IdentityMockType.cs | 4 +- .../IntegrationSettings.cs | 41 ++--- test/Qwiq.Integration.Tests/ProjectTests.cs | 14 +- .../RestWorkItemContextSpecification.cs | 7 +- .../SoapWorkItemContextSpecification.cs | 5 +- ...ItemStoreComparisonContextSpecification.cs | 4 +- test/Qwiq.Integration.Tests/WorkItemTests.cs | 65 +++----- test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs | 2 +- .../PocoMapping.cs | 3 +- .../PocoMappingWithLinks.cs | 3 +- .../Mocks/InstrumentedMockWorkItemStore.cs | 6 +- test/Qwiq.Mapper.Tests/Mocks/MockModel.cs | 2 +- .../Mocks/MockModelWithNoBacking.cs | 2 +- .../Mocks/MockModelWithNoType.cs | 2 +- .../Mocks/SimpleMockModel.cs | 2 +- .../Qwiq.Mapper.Tests.csproj | 16 ++ test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 13 +- test/Qwiq.Mapper.Tests/packages.config | 3 + test/Qwiq.Mocks/Extensions.cs | 17 +- test/Qwiq.Mocks/GlobalSuppressions.cs | Bin 0 -> 7772 bytes test/Qwiq.Mocks/MockFieldDefinition.cs | 3 +- .../MockIdentityManagementService.cs | 93 +++++++---- test/Qwiq.Mocks/MockProject.cs | 36 ++--- test/Qwiq.Mocks/MockQueryFactory.cs | 17 +- .../MockTfsTeamProjectCollection.cs | 6 +- test/Qwiq.Mocks/MockWorkItem.cs | 8 +- test/Qwiq.Mocks/MockWorkItemLinkType.cs | 8 +- test/Qwiq.Mocks/MockWorkItemStore.cs | 23 ++- test/Qwiq.Mocks/MockWorkItemType.cs | 2 + test/Qwiq.Mocks/Properties/AssemblyInfo.cs | 4 +- .../PropertyValueGenerator.cs} | 149 ++++-------------- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 27 ++-- .../Random.Extensions.cs} | 25 +-- test/Qwiq.Mocks/Randomizer.cs | 11 ++ test/Qwiq.Mocks/WorkItemGenerator.cs | 113 +++++++++++++ .../WorkItemLinkGenerator.cs | 12 +- 177 files changed, 1272 insertions(+), 1382 deletions(-) rename src/Qwiq.Core.Rest/{IInternalTfsTeamProjectCollection.cs => IInternalTeamProjectCollection.cs} (72%) delete mode 100644 src/Qwiq.Core.Rest/WorkItemLinkType.cs rename src/Qwiq.Core.Soap/{IInternalTfsTeamProjectCollection.cs => IInternalTeamProjectCollection.cs} (54%) create mode 100644 src/Qwiq.Core/Comparer.cs delete mode 100644 src/Qwiq.Core/Credentials/AuthenticationType.cs create mode 100644 src/Qwiq.Core/Credentials/AuthenticationTypes.cs delete mode 100644 src/Qwiq.Core/Credentials/VssCredentialsComparer.cs rename src/Qwiq.Core/{ => Exceptions}/DeniedOrNotExistException.cs (50%) rename src/Qwiq.Core/{ => Exceptions}/FieldDefinitionNotExistException.cs (66%) rename src/Qwiq.Core/{ => Exceptions}/PageSizeRangeException.cs (55%) create mode 100644 src/Qwiq.Core/Exceptions/WorkItemTypeDeniedOrNotExistException.cs create mode 100644 src/Qwiq.Core/GlobalSuppressions.cs rename src/Qwiq.Core/{IReadOnlyList.cs => IReadOnlyCollection.cs} (74%) rename src/Qwiq.Core/{IReadOnlyListWithId.cs => IReadOnlyCollectionWithId.cs} (60%) create mode 100644 src/Qwiq.Core/IWorkItem.Extensions.cs create mode 100644 src/Qwiq.Core/IWorkItemStore.Extensions.cs create mode 100644 src/Qwiq.Core/IWorkItemType.Extensions.cs rename src/Qwiq.Core/{ReadOnlyList.cs => ReadOnlyCollection.cs} (91%) rename src/Qwiq.Core/{ReadOnlyListWithId.cs => ReadOnlyCollectionWithId.cs} (69%) rename src/Qwiq.Core/{ReadOnlyListWithIdComparer.cs => ReadOnlyCollectionWithIdComparer.cs} (62%) delete mode 100644 src/Qwiq.Core/WorkItemExtensions.cs delete mode 100644 src/Qwiq.Identity/Mapper/IdentifiableComparer.cs create mode 100644 test/Qwiq.Benchmark/GlobalSuppressions.cs delete mode 100644 test/Qwiq.Benchmark/TraceEvent.ReadMe.txt delete mode 100644 test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt delete mode 100644 test/Qwiq.Benchmark/_TraceEventProgrammersGuide.docx create mode 100644 test/Qwiq.Core.Tests/GlobalSuppressions.cs create mode 100644 test/Qwiq.Identity.Benchmark.Tests/GlobalSuppressions.cs create mode 100644 test/Qwiq.Mocks/GlobalSuppressions.cs rename test/{Qwiq.Benchmark/WorkItemGenerator.cs => Qwiq.Mocks/PropertyValueGenerator.cs} (55%) rename test/{Qwiq.Benchmark/Randomizer.cs => Qwiq.Mocks/Random.Extensions.cs} (58%) create mode 100644 test/Qwiq.Mocks/Randomizer.cs create mode 100644 test/Qwiq.Mocks/WorkItemGenerator.cs rename test/{Qwiq.Benchmark => Qwiq.Mocks}/WorkItemLinkGenerator.cs (90%) diff --git a/src/Qwiq.Core.Rest/FieldDefinition.cs b/src/Qwiq.Core.Rest/FieldDefinition.cs index 706fda6b..e17a7efd 100644 --- a/src/Qwiq.Core.Rest/FieldDefinition.cs +++ b/src/Qwiq.Core.Rest/FieldDefinition.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Rest internal class FieldDefinition : Qwiq.FieldDefinition { internal FieldDefinition(WorkItemFieldReference field) - :base(field?.ReferenceName, field?.Name) + :base(field.ReferenceName, field.Name) { if (field == null) throw new ArgumentNullException(nameof(field)); } diff --git a/src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs b/src/Qwiq.Core.Rest/IInternalTeamProjectCollection.cs similarity index 72% rename from src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs rename to src/Qwiq.Core.Rest/IInternalTeamProjectCollection.cs index b5c4847c..567e9f9c 100644 --- a/src/Qwiq.Core.Rest/IInternalTfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Rest/IInternalTeamProjectCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Rest { - internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection + internal interface IInternalTeamProjectCollection : ITeamProjectCollection { T GetClient() where T : VssHttpClientBase; diff --git a/src/Qwiq.Core.Rest/IdentityDescriptor.cs b/src/Qwiq.Core.Rest/IdentityDescriptor.cs index a5167612..4cd3632d 100644 --- a/src/Qwiq.Core.Rest/IdentityDescriptor.cs +++ b/src/Qwiq.Core.Rest/IdentityDescriptor.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Rest public class IdentityDescriptor : Qwiq.IdentityDescriptor { internal IdentityDescriptor(VisualStudio.Services.Identity.IdentityDescriptor descriptor) - : base(descriptor?.IdentityType, descriptor?.Identifier) + : base(descriptor.IdentityType, descriptor.Identifier) { if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index 2a12b07e..adf8635b 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Rest { - internal class LinkCollection : ReadOnlyList, ICollection + internal class LinkCollection : ReadOnlyCollection, ICollection { public LinkCollection(IEnumerable relations, Func linkFunc) { diff --git a/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs b/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs index 49ffc55f..67a08771 100644 --- a/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -22,6 +23,8 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("852f1b8e-336d-4a55-80b8-eed7ddb68619")] +[assembly: CLSCompliant(true)] + [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 80dee82f..98ed15b0 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -24,8 +24,6 @@ internal class Query : IQuery private readonly DateTime? _asOf; - private readonly List _fields; - private readonly Wiql _query; private readonly bool _timePrecision; @@ -94,12 +92,12 @@ public IEnumerable RunQuery() if (_ids == null) yield break; - var expand = _fields != null ? (WorkItemExpand?)null : WorkItemExpand.All; + var expand = WorkItemExpand.All; var qry = _ids.Partition(_workItemStore.PageSize); var ts = qry.Select( s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync( s, - _fields, + null, _asOf, expand, WorkItemErrorPolicy.Omit)); @@ -119,7 +117,8 @@ IWorkItemType WorkItemTypeFactory() yield return ExceptionHandlingDynamicProxyFactory.Create( new WorkItem( workItem, - new Lazy(WorkItemTypeFactory), + new Lazy( + WorkItemTypeFactory), s => _workItemStore.WorkItemLinkTypes[s])); } } @@ -129,8 +128,7 @@ IWorkItemType WorkItemTypeFactory() var m = AsOfRegex.Match(wiql); if (!m.Success) return null; - DateTime retval; - if (!DateTime.TryParse(m.Groups["date"].Value, out retval)) throw new Exception(); + if (!DateTime.TryParse(m.Groups["date"].Value, out DateTime retval)) throw new Exception(); return retval; } diff --git a/src/Qwiq.Core.Rest/QueryFactory.cs b/src/Qwiq.Core.Rest/QueryFactory.cs index a39c2654..ce119385 100644 --- a/src/Qwiq.Core.Rest/QueryFactory.cs +++ b/src/Qwiq.Core.Rest/QueryFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; @@ -31,15 +32,18 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) { // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). // It is used for ASOF - var wiql = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; + FormattableString ws = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; + var wiql = ws.ToString(CultureInfo.InvariantCulture); + if (asOf.HasValue) { // If specified DateTime is not UTC convert it to local time based on TFS client TimeZone if (asOf.Value.Kind != DateTimeKind.Utc) asOf = DateTime.SpecifyKind( - asOf.Value - _store.TimeZone.GetUtcOffset(asOf.Value), - DateTimeKind.Utc); - wiql += $" ASOF \'{asOf.Value:u}\'"; + asOf.Value - _store.TimeZone.GetUtcOffset(asOf.Value), + DateTimeKind.Utc); + FormattableString ao = $" ASOF \'{asOf.Value:u}\'"; + wiql += ao.ToString(CultureInfo.InvariantCulture); } return Create(ids, wiql); diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index 00f9a701..c122676d 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -12,6 +12,16 @@ Microsoft.Qwiq.Core.Rest v4.6 512 + true + ..\ + true + prompt + 4 + CS0108 + ..\..\build\rulesets\ship.ruleset + true + true + 1591 @@ -21,21 +31,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - CS0108 - ..\..\build\rulesets\ship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true @@ -101,7 +103,7 @@ - + @@ -111,7 +113,6 @@ - diff --git a/src/Qwiq.Core.Rest/VssConnectionAdapter.cs b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs index 67effc8f..29a01fdf 100644 --- a/src/Qwiq.Core.Rest/VssConnectionAdapter.cs +++ b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs @@ -1,25 +1,25 @@ using System; -using Microsoft.Qwiq.Credentials; +using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; namespace Microsoft.Qwiq.Rest { - internal class VssConnectionAdapter : IInternalTfsTeamProjectCollection + internal class VssConnectionAdapter : IInternalTeamProjectCollection { private readonly VssConnection _connection; internal VssConnectionAdapter(VssConnection connection) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); - AuthorizedCredentials = new TfsCredentials(connection.Credentials); + AuthorizedCredentials = connection.Credentials; AuthorizedIdentity = new TeamFoundationIdentity(_connection.AuthorizedIdentity); HasAuthenticated = connection.HasAuthenticated; Uri = connection.Uri; TimeZone = TimeZone.CurrentTimeZone; } - public TfsCredentials AuthorizedCredentials { get; } + public VssCredentials AuthorizedCredentials { get; } public ITeamFoundationIdentity AuthorizedIdentity { get; } diff --git a/src/Qwiq.Core.Rest/WorkItemLinkType.cs b/src/Qwiq.Core.Rest/WorkItemLinkType.cs deleted file mode 100644 index 0a8a2a1f..00000000 --- a/src/Qwiq.Core.Rest/WorkItemLinkType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Microsoft.Qwiq.Rest -{ - internal class WorkItemLinkType : Qwiq.WorkItemLinkType - { - internal WorkItemLinkType(string referenceName) - : base(referenceName) - { - } - - internal void SetForwardEnd(IWorkItemLinkTypeEnd forward) - { - _forward = forward ?? throw new ArgumentNullException(nameof(forward)); - } - - internal void SetReverseEnd(IWorkItemLinkTypeEnd reverse) - { - _reverse = reverse ?? throw new ArgumentNullException(nameof(reverse)); - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs index 8e648b1d..54241b59 100644 --- a/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Rest internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd { internal WorkItemLinkTypeEnd(WorkItemRelationType item) - : base(item?.ReferenceName) + : base(item.ReferenceName) { if (item == null) throw new ArgumentNullException(nameof(item)); Name = item.Name; diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 4552e6fb..6ea899d8 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -3,10 +3,10 @@ using System.Linq; using System.Text.RegularExpressions; -using Microsoft.Qwiq.Credentials; using Microsoft.TeamFoundation.Core.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Rest { @@ -22,12 +22,12 @@ internal class WorkItemStore : IWorkItemStore private readonly Lazy _queryFactory; - private readonly Lazy _tfs; + private readonly Lazy _tfs; private readonly Lazy _fieldDefinitions; internal WorkItemStore( - Func tpcFactory, + Func tpcFactory, Func queryFactory, int pageSize = Rest.Query.MaximumBatchSize) : this(tpcFactory, () => tpcFactory()?.GetClient(), queryFactory, pageSize) @@ -35,7 +35,7 @@ internal WorkItemStore( } internal WorkItemStore( - Func tpcFactory, + Func tpcFactory, Func wisFactory, Func queryFactory, int pageSize = Rest.Query.MaximumBatchSize) @@ -43,7 +43,7 @@ internal WorkItemStore( if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); - _tfs = new Lazy(tpcFactory); + _tfs = new Lazy(tpcFactory); NativeWorkItemStore = new Lazy(wisFactory); _queryFactory = new Lazy(() => queryFactory(this)); @@ -78,7 +78,7 @@ WorkItemLinkTypeCollection ValueFactory() internal Lazy NativeWorkItemStore { get; } - public TfsCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; + public VssCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; public ClientType ClientType => ClientType.Rest; @@ -86,7 +86,7 @@ WorkItemLinkTypeCollection ValueFactory() public IProjectCollection Projects => _projects.Value; - public ITfsTeamProjectCollection TeamProjectCollection => _tfs.Value; + public ITeamProjectCollection TeamProjectCollection => _tfs.Value; public TimeZone TimeZone => TeamProjectCollection?.TimeZone ?? TimeZone.CurrentTimeZone; diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index e74d9263..df754d6a 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.Services.WebApi; using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Qwiq.Rest { @@ -24,9 +25,9 @@ public IWorkItemStore Create(AuthenticationOptions options) foreach (var credential in credentials) try { - var tfsNative = ConnectToTfsCollection(options.Uri, credential.Credentials); + var tfsNative = ConnectToTfsCollection(options.Uri, credential); var tfsProxy = - ExceptionHandlingDynamicProxyFactory.Create( + ExceptionHandlingDynamicProxyFactory.Create( new VssConnectionAdapter(tfsNative)); options.Notifications.AuthenticationSuccess( @@ -71,13 +72,7 @@ public IWorkItemStore Create( IEnumerable credentials, ClientType type = ClientType.Default) { - var options = - new AuthenticationOptions(endpoint, AuthenticationType.Windows, type) - { - CreateCredentials = - t => credentials - }; - + var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, type, types => credentials.Select(s=>s.Credentials)); return Create(options); } @@ -97,7 +92,7 @@ private static VssConnection ConnectToTfsCollection(Uri endpoint, VssCredentials return tfsServer; } - private static IWorkItemStore CreateRestWorkItemStore(IInternalTfsTeamProjectCollection tfs) + private static IWorkItemStore CreateRestWorkItemStore(IInternalTeamProjectCollection tfs) { return new WorkItemStore(() => tfs, QueryFactory.GetInstance); } diff --git a/src/Qwiq.Core.Soap/FieldCollection.cs b/src/Qwiq.Core.Soap/FieldCollection.cs index ecc3a81f..d2487833 100644 --- a/src/Qwiq.Core.Soap/FieldCollection.cs +++ b/src/Qwiq.Core.Soap/FieldCollection.cs @@ -123,9 +123,9 @@ public IField this[int index] } } - public bool Equals(IReadOnlyListWithId other) + public bool Equals(IReadOnlyCollectionWithId other) { - return Comparer.FieldCollectionComparer.Equals(this, other); + return Comparer.FieldCollection.Equals(this, other); } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/IInternalTfsTeamProjectCollection.cs b/src/Qwiq.Core.Soap/IInternalTeamProjectCollection.cs similarity index 54% rename from src/Qwiq.Core.Soap/IInternalTfsTeamProjectCollection.cs rename to src/Qwiq.Core.Soap/IInternalTeamProjectCollection.cs index ba72153d..1b531707 100644 --- a/src/Qwiq.Core.Soap/IInternalTfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Soap/IInternalTeamProjectCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Soap { - internal interface IInternalTfsTeamProjectCollection : ITfsTeamProjectCollection, IDisposable + internal interface IInternalTeamProjectCollection : ITeamProjectCollection, IDisposable { T GetClient(); diff --git a/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs b/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs index 35ddc393..ffbaab0e 100644 --- a/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -22,6 +23,8 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("5516d63e-16f0-4f5f-bd9c-76d21b6d8aa3")] +[assembly: CLSCompliant(true)] + [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index a130d9ee..5030dc7e 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -12,6 +12,16 @@ Microsoft.Qwiq.Core.Soap v4.6 512 + true + ..\ + true + prompt + 4 + CS0108 + ..\..\build\rulesets\ship.ruleset + true + true + 1591 @@ -21,21 +31,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - CS0108 - ..\..\build\rulesets\ship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true @@ -203,7 +205,7 @@ - + diff --git a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs index 1a731dc1..9619d946 100644 --- a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs @@ -1,16 +1,16 @@ using System; -using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.TeamFoundation.Server; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Soap { /// /// /// - internal class TfsTeamProjectCollection : IInternalTfsTeamProjectCollection + internal class TfsTeamProjectCollection : IInternalTeamProjectCollection { private readonly Lazy _css; @@ -22,12 +22,12 @@ internal class TfsTeamProjectCollection : IInternalTfsTeamProjectCollection /// Initializes a new instance of the class. /// /// The TFS. - /// tfs + /// tfs internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection teamProjectCollection) { _tfs = teamProjectCollection ?? throw new ArgumentNullException(nameof(teamProjectCollection)); - AuthorizedCredentials = new TfsCredentials(_tfs.ClientCredentials); + AuthorizedCredentials = _tfs.ClientCredentials; AuthorizedIdentity = new TeamFoundationIdentity(_tfs.AuthorizedIdentity); Uri = _tfs.Uri; @@ -43,7 +43,7 @@ internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection /// Gets the credentials for this project collection. /// /// The authorized credentials. - public TfsCredentials AuthorizedCredentials { get; } + public VssCredentials AuthorizedCredentials { get; } /// /// The identity who the calls to the server are being made for. diff --git a/src/Qwiq.Core.Soap/WorkItem.cs b/src/Qwiq.Core.Soap/WorkItem.cs index e05b7c2b..69ba1bfc 100644 --- a/src/Qwiq.Core.Soap/WorkItem.cs +++ b/src/Qwiq.Core.Soap/WorkItem.cs @@ -107,7 +107,7 @@ public override string History /// /// Gets the number of hyperlinks in this work item. /// - public new int HyperLinkCount => _item.HyperLinkCount; + public new int HyperlinkCount => _item.HyperLinkCount; /// /// Gets the ID of this work item. diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index d62666ce..f2738bb8 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.Common; +using Microsoft.VisualStudio.Services.Common; using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -22,14 +22,14 @@ internal class WorkItemStore : IWorkItemStore private readonly Lazy _queryFactory; - private readonly Lazy _tfs; + private readonly Lazy _tfs; private readonly Lazy _workItemStore; private readonly Lazy _projects; internal WorkItemStore( - Func tpcFactory, + Func tpcFactory, Func wisFactory, Func queryFactory, int pageSize = PageSizeLimits.MaxPageSize) @@ -41,7 +41,7 @@ internal WorkItemStore( if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) throw new PageSizeRangeException(); - _tfs = new Lazy(tpcFactory); + _tfs = new Lazy(tpcFactory); _workItemStore = new Lazy(wisFactory); _queryFactory = new Lazy(() => queryFactory(this)); @@ -61,7 +61,7 @@ internal WorkItemStore( } internal WorkItemStore( - Func tpcFactory, + Func tpcFactory, Func queryFactory, int pageSize = PageSizeLimits.MaxPageSize) : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory, pageSize) @@ -72,7 +72,7 @@ internal WorkItemStore( public ClientType ClientType => ClientType.Soap; - public TfsCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; + public VssCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; @@ -82,7 +82,7 @@ internal WorkItemStore( public IProjectCollection Projects => _projects.Value; - public ITfsTeamProjectCollection TeamProjectCollection => _tfs.Value; + public ITeamProjectCollection TeamProjectCollection => _tfs.Value; public TimeZone TimeZone => _workItemStore.Value.TimeZone; diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 0746420d..069ad735 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; @@ -27,9 +28,9 @@ public IWorkItemStore Create(AuthenticationOptions options) foreach (var credential in credentials) try { - var tfsNative = ConnectToTfsCollection(options.Uri, credential.Credentials); + var tfsNative = ConnectToTfsCollection(options.Uri, credential); var tfsProxy = - ExceptionHandlingDynamicProxyFactory.Create( + ExceptionHandlingDynamicProxyFactory.Create( new TfsTeamProjectCollection(tfsNative)); options.Notifications.AuthenticationSuccess( @@ -68,13 +69,7 @@ public IWorkItemStore Create( IEnumerable credentials, ClientType type = ClientType.Default) { - var options = - new AuthenticationOptions(endpoint, AuthenticationType.Windows, type) - { - CreateCredentials = - t => credentials - }; - + var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, type, types => credentials.Select(s=>s.Credentials)); return Create(options); } @@ -93,7 +88,7 @@ private static TeamFoundation.Client.TfsTeamProjectCollection ConnectToTfsCollec return tfsServer; } - private static IWorkItemStore CreateSoapWorkItemStore(IInternalTfsTeamProjectCollection tfs) + private static IWorkItemStore CreateSoapWorkItemStore(IInternalTeamProjectCollection tfs) { return new WorkItemStore(() => tfs, store => new QueryFactory(store)); } diff --git a/src/Qwiq.Core/BaseLinkType.cs b/src/Qwiq.Core/BaseLinkType.cs index 9395cb09..58e96a05 100644 --- a/src/Qwiq.Core/BaseLinkType.cs +++ b/src/Qwiq.Core/BaseLinkType.cs @@ -2,6 +2,7 @@ namespace Microsoft.Qwiq { public enum BaseLinkType { + None = 0, Hyperlink = 1, RelatedLink = 2, ExternalLink = 5 diff --git a/src/Qwiq.Core/ClientType.cs b/src/Qwiq.Core/ClientType.cs index 376b1f6e..af7ac5cb 100644 --- a/src/Qwiq.Core/ClientType.cs +++ b/src/Qwiq.Core/ClientType.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq /// /// Enum ClientType /// - public enum ClientType : short + public enum ClientType { /// /// No remote communications are made. diff --git a/src/Qwiq.Core/Comparer.cs b/src/Qwiq.Core/Comparer.cs new file mode 100644 index 00000000..cae1b11f --- /dev/null +++ b/src/Qwiq.Core/Comparer.cs @@ -0,0 +1,22 @@ +using System; + +namespace Microsoft.Qwiq +{ + public static class Comparer + { + public static readonly ReadOnlyCollectionWithIdComparer FieldCollection = + ReadOnlyCollectionWithIdComparer.Default; + + public static readonly FieldDefinitionCollectionComparer FieldDefinitionCollection = FieldDefinitionCollectionComparer.Default; + + public static readonly IdentifiableComparer Identifiable = IdentifiableComparer.Default; + + public static readonly IdentityDescriptorComparer IdentityDescriptor = IdentityDescriptorComparer.Default; + + public static readonly ReadOnlyCollectionWithIdComparer NodeCollection = + ReadOnlyCollectionWithIdComparer.Default; + + public static readonly WorkItemLinkTypeEndComparer WorkItemLinkTypeEnd = WorkItemLinkTypeEndComparer.Default; + public static readonly StringComparer OrdinalIgnoreCase = StringComparer.OrdinalIgnoreCase; + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/CoreField.cs b/src/Qwiq.Core/CoreField.cs index 00ee47ea..1b1729a5 100644 --- a/src/Qwiq.Core/CoreField.cs +++ b/src/Qwiq.Core/CoreField.cs @@ -7,7 +7,7 @@ public enum CoreField IterationId = -104, ExternalLinkCount = -57, TeamProject = -42, - HyperLinkCount = -32, + HyperlinkCount = -32, AttachedFileCount = -31, NodeName = -12, AreaPath = -7, diff --git a/src/Qwiq.Core/CoreFieldRefNames.cs b/src/Qwiq.Core/CoreFieldRefNames.cs index 749c470f..4cea2e78 100644 --- a/src/Qwiq.Core/CoreFieldRefNames.cs +++ b/src/Qwiq.Core/CoreFieldRefNames.cs @@ -38,7 +38,7 @@ public static class CoreFieldRefNames public const string History = "System.History"; - public const string HyperLinkCount = "System.HyperLinkCount"; + public const string HyperlinkCount = "System.HyperLinkCount"; public const string Id = "System.Id"; @@ -93,7 +93,7 @@ public static class CoreFieldRefNames { Description, (int)CoreField.Description }, { ExternalLinkCount, (int)CoreField.ExternalLinkCount }, { History, (int)CoreField.History }, - { HyperLinkCount, (int)CoreField.HyperLinkCount }, + { HyperlinkCount, (int)CoreField.HyperlinkCount }, { Id, (int)CoreField.Id }, { IterationId, (int)CoreField.IterationId }, { IterationPath, (int)CoreField.IterationPath }, @@ -133,7 +133,7 @@ public static class CoreFieldRefNames { Description, "Description" }, { ExternalLinkCount, "External Link Count" }, { History, "History" }, - { HyperLinkCount, "Hyperlink Count" }, + { HyperlinkCount, "Hyperlink Count" }, { Id, "ID" }, { IterationId, "Iteration ID" }, { IterationPath, "Iteration Path" }, diff --git a/src/Qwiq.Core/Credentials/AuthenticationFailedNotification.cs b/src/Qwiq.Core/Credentials/AuthenticationFailedNotification.cs index 87af9e62..ccf8e610 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationFailedNotification.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationFailedNotification.cs @@ -1,21 +1,28 @@ using System; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Credentials { public class AuthenticationFailedNotification : CredentialNotification { - public AuthenticationFailedNotification(TfsCredentials credentials, Exception exception) + public AuthenticationFailedNotification(VssCredentials credentials, Exception exception) : this(credentials) { Exception = exception; } - public AuthenticationFailedNotification(TfsCredentials credentials) + public AuthenticationFailedNotification(VssCredentials credentials) : base(credentials) { + } + public AuthenticationFailedNotification(Exception exception) + :base(null) + { + Exception = exception; } - public Exception Exception { get; set; } + public Exception Exception { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs index 0c895518..2e844fa0 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs @@ -1,72 +1,117 @@ using System; using System.Collections.Generic; -using System.Linq; + +using Microsoft.VisualStudio.Services.Client; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Credentials { public class AuthenticationOptions { - public Uri Uri { get; } - - public AuthenticationType AuthenticationType { get; } - - public ClientType ClientType { get; set; } + private readonly Func> _createCredentials; public AuthenticationOptions(string uri) : this(new Uri(uri, UriKind.Absolute)) { } - public AuthenticationOptions(Uri uri, AuthenticationType authenticationType = AuthenticationType.All, ClientType clientType = ClientType.Default) + public AuthenticationOptions( + string uri, + AuthenticationTypes authenticationTypes = AuthenticationTypes.All, + ClientType clientType = ClientType.Default) + : this(new Uri(uri, UriKind.Absolute), authenticationTypes, clientType) { - if (uri == null) throw new ArgumentNullException(nameof(uri)); + } + public AuthenticationOptions(Uri uri) + : this(uri, AuthenticationTypes.All, ClientType.Default) + { + } - AuthenticationType = authenticationType; + public AuthenticationOptions( + Uri uri, + AuthenticationTypes authenticationTypes, + ClientType clientType, + Func> credentialsFactory = null) + { + AuthenticationTypes = authenticationTypes; ClientType = clientType; Notifications = new CredentialsNotifications(); - Uri = uri; + Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + _createCredentials = credentialsFactory ?? CredentialsFactory; } - public CredentialsNotifications Notifications { get; set; } + public AuthenticationTypes AuthenticationTypes { get; } - public Func> CreateCredentials { get; set; } + public ClientType ClientType { get; } - public IEnumerable Credentials + public IEnumerable Credentials { get { - foreach (var credential in EnumerateCredentials(this, AuthenticationType.OAuth)) yield return credential; - foreach (var credential in EnumerateCredentials(this, AuthenticationType.PersonalAccessToken)) - yield return credential; - foreach (var credential in EnumerateCredentials(this, AuthenticationType.Basic)) yield return credential; - foreach (var credential in EnumerateCredentials(this, AuthenticationType.Windows)) - yield return credential; - foreach (var credential in EnumerateCredentials(this, AuthenticationType.Anonymous)) - yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationTypes.OpenAuthorization)) yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationTypes.PersonalAccessToken)) yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationTypes.Basic)) yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationTypes.Windows)) yield return credential; + foreach (var credential in EnumerateCredentials(this, AuthenticationTypes.None)) yield return credential; + } + } + + public CredentialsNotifications Notifications { get; set; } + + public Uri Uri { get; } + + private static IEnumerable CredentialsFactory(AuthenticationTypes t) + { + if (t.HasFlag(AuthenticationTypes.OpenAuthorization)) + foreach (var cred in Qwiq.Credentials.CredentialsFactory.GetOAuthCredentials()) yield return cred; + + if (t.HasFlag(AuthenticationTypes.PersonalAccessToken)) + foreach (var cred in Qwiq.Credentials.CredentialsFactory.GetServiceIdentityPatCredentials()) yield return cred; + + if (t.HasFlag(AuthenticationTypes.Windows)) + foreach (var cred in Qwiq.Credentials.CredentialsFactory.GetServiceIdentityCredentials()) yield return cred; + + if (t.HasFlag(AuthenticationTypes.Basic)) + foreach (var cred in Qwiq.Credentials.CredentialsFactory.GetBasicCredentials()) yield return cred; + + if (t.HasFlag(AuthenticationTypes.Windows)) + { + // User did not specify a username or a password, so use the process identity + yield return new VssClientCredentials(new WindowsCredential(false)) + { + Storage = new VssClientCredentialStorage(), + PromptType = CredentialPromptType.DoNotPrompt + }; + + // Use the Windows identity of the logged on user + yield return new VssClientCredentials(true) + { + Storage = new VssClientCredentialStorage(), + PromptType = CredentialPromptType.PromptIfNeeded + }; } } - private static IEnumerable EnumerateCredentials( + private static IEnumerable EnumerateCredentials( AuthenticationOptions authenticationOptions, - AuthenticationType authenticationType) + AuthenticationTypes authenticationTypes) { - if (!authenticationOptions.AuthenticationType.HasFlag(authenticationType)) yield break; + if (!authenticationOptions.AuthenticationTypes.HasFlag(authenticationTypes)) yield break; - var credentials = Enumerable.Empty(); + IEnumerable credentials; try { - credentials = authenticationOptions.CreateCredentials(authenticationType); + credentials = authenticationOptions._createCredentials(authenticationTypes); } catch (Exception e) { - authenticationOptions.Notifications.AuthenticationFailed( - new AuthenticationFailedNotification(null) { Exception = e }); + authenticationOptions.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(e)); throw; } foreach (var credential in credentials) yield return credential; } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/AuthenticationSuccessNotification.cs b/src/Qwiq.Core/Credentials/AuthenticationSuccessNotification.cs index 510a40ea..0dcfc9c0 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationSuccessNotification.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationSuccessNotification.cs @@ -1,10 +1,12 @@ +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Credentials { public class AuthenticationSuccessNotification : CredentialNotification { - public ITfsTeamProjectCollection TeamProjectCollection { get; } + public ITeamProjectCollection TeamProjectCollection { get; } - public AuthenticationSuccessNotification(TfsCredentials credentials, ITfsTeamProjectCollection teamProjectCollection) + public AuthenticationSuccessNotification(VssCredentials credentials, ITeamProjectCollection teamProjectCollection) : base(credentials) { TeamProjectCollection = teamProjectCollection; diff --git a/src/Qwiq.Core/Credentials/AuthenticationType.cs b/src/Qwiq.Core/Credentials/AuthenticationType.cs deleted file mode 100644 index 730e2cb9..00000000 --- a/src/Qwiq.Core/Credentials/AuthenticationType.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Microsoft.Qwiq.Credentials -{ - [Flags] - public enum AuthenticationType - { - Anonymous, - OAuth, - PersonalAccessToken, - Basic, - Windows, - All = Anonymous | OAuth | PersonalAccessToken | Basic | Windows - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/AuthenticationTypes.cs b/src/Qwiq.Core/Credentials/AuthenticationTypes.cs new file mode 100644 index 00000000..1735689d --- /dev/null +++ b/src/Qwiq.Core/Credentials/AuthenticationTypes.cs @@ -0,0 +1,15 @@ +using System; + +namespace Microsoft.Qwiq.Credentials +{ + [Flags] + public enum AuthenticationTypes + { + None, + OpenAuthorization, + PersonalAccessToken, + Basic, + Windows, + All = None | OpenAuthorization | PersonalAccessToken | Basic | Windows + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/CredentialNotification.cs b/src/Qwiq.Core/Credentials/CredentialNotification.cs index dde49884..bbdce3fd 100644 --- a/src/Qwiq.Core/Credentials/CredentialNotification.cs +++ b/src/Qwiq.Core/Credentials/CredentialNotification.cs @@ -1,9 +1,11 @@ +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Credentials { public class CredentialNotification { - public TfsCredentials Credentials { get; } - public CredentialNotification(TfsCredentials credentials) + public VssCredentials Credentials { get; } + public CredentialNotification(VssCredentials credentials) { Credentials = credentials; } diff --git a/src/Qwiq.Core/Credentials/CredentialsFactory.cs b/src/Qwiq.Core/Credentials/CredentialsFactory.cs index 6ae613cb..8c951154 100644 --- a/src/Qwiq.Core/Credentials/CredentialsFactory.cs +++ b/src/Qwiq.Core/Credentials/CredentialsFactory.cs @@ -18,9 +18,6 @@ namespace Microsoft.Qwiq.Credentials /// can try them /// in order and just go with the first one that succeeds. /// - [Obsolete( - "This class is deprecated and will be removed in a future release. See AuthenticationOptions instead.", - false)] public static class CredentialsFactory { [Obsolete( @@ -84,7 +81,7 @@ private static IEnumerable CreateCredentialsImpl( }; } - private static IEnumerable GetBasicCredentials(string username = null, string password = null) + internal static IEnumerable GetBasicCredentials(string username = null, string password = null) { if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) yield break; @@ -94,7 +91,7 @@ private static IEnumerable GetBasicCredentials(string username = }; } - private static IEnumerable GetOAuthCredentials(string accessToken = null) + internal static IEnumerable GetOAuthCredentials(string accessToken = null) { if (string.IsNullOrEmpty(accessToken)) yield break; @@ -105,7 +102,7 @@ private static IEnumerable GetOAuthCredentials(string accessToke }; } - private static IEnumerable GetServiceIdentityCredentials( + internal static IEnumerable GetServiceIdentityCredentials( string username = null, string password = null) { @@ -120,7 +117,7 @@ private static IEnumerable GetServiceIdentityCredentials( CredentialPromptType.DoNotPrompt); } - private static IEnumerable GetServiceIdentityPatCredentials(string password = null) + internal static IEnumerable GetServiceIdentityPatCredentials(string password = null) { if (string.IsNullOrEmpty(password)) yield break; diff --git a/src/Qwiq.Core/Credentials/CredentialsNotifications.cs b/src/Qwiq.Core/Credentials/CredentialsNotifications.cs index be134659..74669edc 100644 --- a/src/Qwiq.Core/Credentials/CredentialsNotifications.cs +++ b/src/Qwiq.Core/Credentials/CredentialsNotifications.cs @@ -1,54 +1,63 @@ using System; using System.Diagnostics; +using System.Globalization; using System.Threading.Tasks; namespace Microsoft.Qwiq.Credentials { /// - /// Specifies events which the invokes to enable developer control over the authentication process. + /// Specifies events which the invokes to enable developer control over the + /// authentication process. /// public class CredentialsNotifications { public CredentialsNotifications() + : this(null, null) { - AuthenticationFailed = n => - { - var credential = n.Credentials; - var e = n.Exception; - - if (credential != null) - { - - Trace.TraceWarning( - "TFS connection attempt failed with {0}/{1}.\n Exception: {2}", - credential.Credentials.Windows?.GetType(), - credential.Credentials.Federated?.GetType(), - e?.ToString() ?? ""); - } - else - { - Trace.TraceError("TFS connection attempt failed. {0}", e?.ToString() ?? "No additional information."); - } - - return Task.CompletedTask; - }; - - AuthenticationSuccess = n => - { - var credential = n.Credentials; - - Trace.TraceInformation( - $"Connected to {n.TeamProjectCollection.Uri}: {n.TeamProjectCollection.AuthorizedIdentity.UniqueName}"); - - Trace.TraceInformation( - "TFS connection attempt success with {0}/{1}.", - credential.Credentials.Windows.GetType(), - credential.Credentials.Federated.GetType()); - - return Task.CompletedTask; - }; } - public Func AuthenticationFailed { get; set; } - public Func AuthenticationSuccess { get; set; } + + public CredentialsNotifications( + Func success, + Func failed) + { + AuthenticationSuccess = success ?? SuccessAsync; + AuthenticationFailed = failed ?? FailedAsync; + } + + public Func AuthenticationFailed { get; } + + public Func AuthenticationSuccess { get; } + + private static Task FailedAsync(AuthenticationFailedNotification n) + { + var credential = n.Credentials; + var e = n.Exception; + + if (credential != null) + Trace.TraceWarning( + "TFS connection attempt failed with {0}/{1}.\n Exception: {2}", + credential.Windows?.GetType(), + credential.Federated?.GetType(), + e?.ToString() ?? ""); + else Trace.TraceError("TFS connection attempt failed. {0}", e?.ToString() ?? "No additional information."); + + return Task.CompletedTask; + } + + private static Task SuccessAsync(AuthenticationSuccessNotification n) + { + var credential = n.Credentials; + + FormattableString s = $"Connected to {n.TeamProjectCollection.Uri}: {n.TeamProjectCollection.AuthorizedIdentity.UniqueName}"; + + Trace.TraceInformation(s.ToString(CultureInfo.InvariantCulture)); + + Trace.TraceInformation( + "TFS connection attempt success with {0}/{1}.", + credential.Windows.GetType(), + credential.Federated.GetType()); + + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/TfsCredentials.cs b/src/Qwiq.Core/Credentials/TfsCredentials.cs index 7abbf7f4..21a7d5d2 100644 --- a/src/Qwiq.Core/Credentials/TfsCredentials.cs +++ b/src/Qwiq.Core/Credentials/TfsCredentials.cs @@ -5,34 +5,15 @@ namespace Microsoft.Qwiq.Credentials { + [Obsolete("This type will be removed in a future release. Use VssCredentials instead.")] [DebuggerStepThrough] - public sealed class TfsCredentials : IEquatable, IEquatable + public sealed class TfsCredentials { public TfsCredentials(VssCredentials credentials) { Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); } - public bool Equals(TfsCredentials other) - { - return VssCredentialsComparer.Instance.Equals(Credentials, other?.Credentials); - } - - public bool Equals(VssCredentials other) - { - return VssCredentialsComparer.Instance.Equals(Credentials, other); - } - - public override bool Equals(object obj) - { - return VssCredentialsComparer.Instance.Equals(Credentials, (obj as TfsCredentials)?.Credentials); - } - - public override int GetHashCode() - { - return VssCredentialsComparer.Instance.GetHashCode(Credentials); - } - internal VssCredentials Credentials { get; } public static implicit operator TfsCredentials(VssCredentials credentials) diff --git a/src/Qwiq.Core/Credentials/VssCredentialsComparer.cs b/src/Qwiq.Core/Credentials/VssCredentialsComparer.cs deleted file mode 100644 index 37d26f88..00000000 --- a/src/Qwiq.Core/Credentials/VssCredentialsComparer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.VisualStudio.Services.Common; - -namespace Microsoft.Qwiq.Credentials -{ - public class VssCredentialsComparer : GenericComparer - { - public static VssCredentialsComparer Instance => Nested.Instance; - - private VssCredentialsComparer() - { - } - - public override bool Equals(VssCredentials x, VssCredentials y) - { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - - - return GenericComparer.Default.Equals(x.Windows, y.Windows) - && GenericComparer.Default.Equals(x.Federated, y.Federated); - } - - public override int GetHashCode(VssCredentials obj) - { - unchecked - { - var hash = 27; - hash = (13 * hash) ^ (obj.Windows != null ? obj.Windows.GetHashCode() : 0); - hash = (13 * hash) ^ (obj.Federated != null ? obj.Federated.GetHashCode() : 0); - - return hash; - } - } - - // ReSharper disable ClassNeverInstantiated.Local - private class Nested - // ReSharper restore ClassNeverInstantiated.Local - { - // ReSharper disable MemberHidesStaticFromOuterClass - internal static readonly VssCredentialsComparer Instance = new VssCredentialsComparer(); - // ReSharper restore MemberHidesStaticFromOuterClass - - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static Nested() - { - } - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/DeniedOrNotExistException.cs b/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs similarity index 50% rename from src/Qwiq.Core/DeniedOrNotExistException.cs rename to src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs index fa427ebe..7a565f56 100644 --- a/src/Qwiq.Core/DeniedOrNotExistException.cs +++ b/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs @@ -1,13 +1,13 @@ using System; +using System.Globalization; using System.Runtime.Serialization; namespace Microsoft.Qwiq { [Serializable] - public class DeniedOrNotExistException : ApplicationException + public class DeniedOrNotExistException : Exception { - private static readonly string ErrorFormat = - "TF26193: The team project {0} does not exist. Check the team project name and try again."; + private const string ErrorFormat = "TF26193: The team project {0} does not exist. Check the team project name and try again."; public DeniedOrNotExistException() : base("TF237090: Does not exist or access is denied.") @@ -15,12 +15,13 @@ public DeniedOrNotExistException() } public DeniedOrNotExistException(string projectName) - : base(string.Format(ErrorFormat, projectName)) + : base(string.Format(CultureInfo.InvariantCulture, ErrorFormat, projectName)) { } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")] public DeniedOrNotExistException(Guid projectGuid) - : base(string.Format(ErrorFormat, projectGuid)) + : base(string.Format(CultureInfo.InvariantCulture, ErrorFormat, projectGuid)) { } diff --git a/src/Qwiq.Core/FieldDefinitionNotExistException.cs b/src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs similarity index 66% rename from src/Qwiq.Core/FieldDefinitionNotExistException.cs rename to src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs index a4910590..d20773e9 100644 --- a/src/Qwiq.Core/FieldDefinitionNotExistException.cs +++ b/src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs @@ -1,7 +1,9 @@ using System; +using System.Runtime.Serialization; namespace Microsoft.Qwiq { + [Serializable] public class FieldDefinitionNotExistException : InvalidOperationException { public FieldDefinitionNotExistException(string name) @@ -9,5 +11,10 @@ public FieldDefinitionNotExistException(string name) { } + + protected FieldDefinitionNotExistException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs b/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs index de762422..7f4a8ae4 100644 --- a/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs +++ b/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs @@ -10,6 +10,7 @@ internal class InnerExceptionExploder : IExceptionExploder { public IEnumerable Explode(Exception exception) { + if (exception == null) Enumerable.Empty(); if (exception.InnerException != null) return new[] { exception.InnerException}; return Enumerable.Empty(); } diff --git a/src/Qwiq.Core/PageSizeRangeException.cs b/src/Qwiq.Core/Exceptions/PageSizeRangeException.cs similarity index 55% rename from src/Qwiq.Core/PageSizeRangeException.cs rename to src/Qwiq.Core/Exceptions/PageSizeRangeException.cs index 5f86c87f..bfcbd414 100644 --- a/src/Qwiq.Core/PageSizeRangeException.cs +++ b/src/Qwiq.Core/Exceptions/PageSizeRangeException.cs @@ -1,7 +1,9 @@ using System; +using System.Runtime.Serialization; namespace Microsoft.Qwiq { + [Serializable] public class PageSizeRangeException : ApplicationException { public PageSizeRangeException() @@ -9,5 +11,10 @@ public PageSizeRangeException() { } + + protected PageSizeRangeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/src/Qwiq.Core/Exceptions/TransientException.cs b/src/Qwiq.Core/Exceptions/TransientException.cs index 53868167..c4d8b520 100644 --- a/src/Qwiq.Core/Exceptions/TransientException.cs +++ b/src/Qwiq.Core/Exceptions/TransientException.cs @@ -1,14 +1,21 @@ using System; using System.Diagnostics; +using System.Runtime.Serialization; namespace Microsoft.Qwiq.Exceptions { + [Serializable] [DebuggerStepThrough] public class TransientException : Exception { - public TransientException(string message, Exception innerException) : base(message, innerException) + public TransientException(string message, Exception innerException) + : base(message, innerException) { } - } -} + protected TransientException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Exceptions/VssExceptionMapper.cs b/src/Qwiq.Core/Exceptions/VssExceptionMapper.cs index 382e57f0..8d102c3c 100644 --- a/src/Qwiq.Core/Exceptions/VssExceptionMapper.cs +++ b/src/Qwiq.Core/Exceptions/VssExceptionMapper.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using Microsoft.VisualStudio.Services.Common; @@ -33,7 +34,7 @@ public Exception Map(Exception ex) return null; } - var errorCode = int.Parse(regexMatch.Groups[ErrorCode].Value); + var errorCode = int.Parse(regexMatch.Groups[ErrorCode].Value, CultureInfo.InvariantCulture); return _handledErrorCodes.Contains(errorCode) ? _newExceptionCreator(vssException.Message, vssException) : null; } diff --git a/src/Qwiq.Core/Exceptions/WorkItemTypeDeniedOrNotExistException.cs b/src/Qwiq.Core/Exceptions/WorkItemTypeDeniedOrNotExistException.cs new file mode 100644 index 00000000..8f2b44d5 --- /dev/null +++ b/src/Qwiq.Core/Exceptions/WorkItemTypeDeniedOrNotExistException.cs @@ -0,0 +1,10 @@ +using System; + +namespace Microsoft.Qwiq +{ + [Serializable] + public class WorkItemTypeDeniedOrNotExistException : DeniedOrNotExistException + { + + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/FieldCollection.cs b/src/Qwiq.Core/FieldCollection.cs index c7839557..8a272cc8 100644 --- a/src/Qwiq.Core/FieldCollection.cs +++ b/src/Qwiq.Core/FieldCollection.cs @@ -65,16 +65,16 @@ public bool Contains(string name) return _definitions.Contains(name); } // REVIEW: Catch a more specific exception - catch (Exception) + catch (WorkItemTypeDeniedOrNotExistException) { return false; } } [DebuggerStepThrough] - public bool Equals(IReadOnlyListWithId other) + public bool Equals(IReadOnlyCollectionWithId other) { - return Comparer.FieldCollectionComparer.Equals(this, other); + return Comparer.FieldCollection.Equals(this, other); } public virtual IField GetById(int id) @@ -114,8 +114,7 @@ public bool TryGetById(int id, out IField value) return true; } } - // REVIEW: Catch a more specific exception - catch (Exception) + catch (WorkItemTypeDeniedOrNotExistException) { } return false; diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs index a0647e99..e8a9b24d 100644 --- a/src/Qwiq.Core/FieldDefinition.cs +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -21,7 +21,7 @@ internal FieldDefinition(int id, string referenceName, string name) if (id == 0) { if (!CoreFieldRefNames.CoreFieldIdLookup.TryGetValue(referenceName, out int fid)) - fid = FieldDefinitionComparer.Instance.GetHashCode(this); + fid = FieldDefinitionComparer.Default.GetHashCode(this); Id = fid; } else @@ -42,14 +42,14 @@ internal FieldDefinition(string referenceName, string name) ReferenceName = referenceName; if (!CoreFieldRefNames.CoreFieldIdLookup.TryGetValue(referenceName, out int id)) - id = FieldDefinitionComparer.Instance.GetHashCode(this); + id = FieldDefinitionComparer.Default.GetHashCode(this); Id = id; } public bool Equals(IFieldDefinition other) { - return FieldDefinitionComparer.Instance.Equals(this, other); + return FieldDefinitionComparer.Default.Equals(this, other); } public int Id { get; } @@ -60,12 +60,12 @@ public bool Equals(IFieldDefinition other) public override bool Equals(object obj) { - return FieldDefinitionComparer.Instance.Equals(this, obj as IFieldDefinition); + return FieldDefinitionComparer.Default.Equals(this, obj as IFieldDefinition); } public override int GetHashCode() { - return FieldDefinitionComparer.Instance.GetHashCode(this); + return FieldDefinitionComparer.Default.GetHashCode(this); } public override string ToString() diff --git a/src/Qwiq.Core/FieldDefinitionCollection.cs b/src/Qwiq.Core/FieldDefinitionCollection.cs index a83278c8..32145505 100644 --- a/src/Qwiq.Core/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core/FieldDefinitionCollection.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Microsoft.Qwiq { - public abstract class FieldDefinitionCollection : ReadOnlyListWithId, - IFieldDefinitionCollection + public abstract class FieldDefinitionCollection : ReadOnlyCollectionWithId, IFieldDefinitionCollection { protected internal FieldDefinitionCollection(IEnumerable fieldDefinitions) : base(fieldDefinitions, definition => definition.Name) @@ -12,21 +12,22 @@ protected internal FieldDefinitionCollection(IEnumerable field public bool Equals(IFieldDefinitionCollection other) { - return FieldDefinitionCollectionComparer.Instance.Equals(this, other); + return Comparer.FieldDefinitionCollection.Equals(this, other); } public override bool Equals(object obj) { - return FieldDefinitionCollectionComparer.Instance.Equals(this, obj as IFieldDefinitionCollection); + return Comparer.FieldDefinitionCollection.Equals(this, obj as IFieldDefinitionCollection); } public override int GetHashCode() { - return FieldDefinitionCollectionComparer.Instance.GetHashCode(this); + return Comparer.FieldDefinitionCollection.GetHashCode(this); } protected override void Add(IFieldDefinition value, int index) { + if (value == null) throw new ArgumentNullException(nameof(value)); base.Add(value, index); AddByName(value.ReferenceName, index); } diff --git a/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs b/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs index c9599b59..9372a30a 100644 --- a/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs @@ -19,7 +19,7 @@ private FieldDefinitionCollectionComparer() { } - public static FieldDefinitionCollectionComparer Instance => Nested.Instance; + internal new static FieldDefinitionCollectionComparer Default => Nested.Instance; public override bool Equals(IFieldDefinitionCollection x, IFieldDefinitionCollection y) { @@ -44,7 +44,7 @@ public override bool Equals(IFieldDefinitionCollection x, IFieldDefinitionCollec if (!y.Contains(field.ReferenceName)) return false; var tf = y[field.ReferenceName]; - if (!FieldDefinitionComparer.Instance.Equals(field, tf)) return false; + if (!FieldDefinitionComparer.Default.Equals(field, tf)) return false; // Removes the first occurrence, so if there are duplicates we'll still get a valid mismatch source.Remove(field); @@ -71,6 +71,7 @@ public override int GetHashCode(IFieldDefinitionCollection obj) return hash; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested { // ReSharper disable MemberHidesStaticFromOuterClass @@ -81,6 +82,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/FieldDefinitionComparer.cs b/src/Qwiq.Core/FieldDefinitionComparer.cs index 881ca44a..ffaa8cd0 100644 --- a/src/Qwiq.Core/FieldDefinitionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionComparer.cs @@ -4,7 +4,12 @@ namespace Microsoft.Qwiq { public class FieldDefinitionComparer : GenericComparer { - public static FieldDefinitionComparer Instance => Nested.Instance; + private FieldDefinitionComparer() + { + + } + + internal new static FieldDefinitionComparer Default => Nested.Instance; public override bool Equals(IFieldDefinition x, IFieldDefinition y) { @@ -34,6 +39,7 @@ public override int GetHashCode(IFieldDefinition obj) } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested { // ReSharper disable MemberHidesStaticFromOuterClass @@ -43,6 +49,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/GenericComparer.cs b/src/Qwiq.Core/GenericComparer.cs index 48348b54..7bd8c531 100644 --- a/src/Qwiq.Core/GenericComparer.cs +++ b/src/Qwiq.Core/GenericComparer.cs @@ -6,25 +6,25 @@ namespace Microsoft.Qwiq { public class GenericComparer : IComparer, IEqualityComparer { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly GenericComparer Default = new GenericComparer(); public virtual int Compare(T x, T y) { - Type type = typeof(T); - // Enumerable? var enumerableX = x as IEnumerable; var enumerableY = y as IEnumerable; if (enumerableX != null && enumerableY != null) { - IEnumerator enumeratorX = enumerableX.GetEnumerator(); - IEnumerator enumeratorY = enumerableY.GetEnumerator(); + var enumeratorX = enumerableX.GetEnumerator(); + var enumeratorY = enumerableY.GetEnumerator(); while (true) { - bool hasNextX = enumeratorX.MoveNext(); - bool hasNextY = enumeratorY.MoveNext(); + var hasNextX = enumeratorX.MoveNext(); + var hasNextY = enumeratorY.MoveNext(); if (!hasNextX || !hasNextY) { @@ -38,6 +38,8 @@ public virtual int Compare(T x, T y) } } + var type = typeof(T); + // Null? if (!type.IsValueType || (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Nullable<>)))) @@ -59,25 +61,22 @@ public virtual int Compare(T x, T y) } // Implements IComparable? - var comparable1 = x as IComparable; - if (comparable1 != null) + if (x is IComparable comparable1) { return comparable1.CompareTo(y); } // Implements IComparable? - var comparable2 = x as IComparable; - if (comparable2 != null) + if (x is IComparable comparable2) { return comparable2.CompareTo(y); } // Implements IEquatable? - var equatable = x as IEquatable; - if (equatable != null) + if (x is IEquatable equatable) { return equatable.Equals(y) ? 0 : -1; } diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..eb8e44536757895faf4f0c0a12a2876164c2f691 GIT binary patch literal 17670 zcmeI4Yi|=r6o%(>rT&Li`UO-lAwXN97AX)yEfokPfLervu{Wt(#|B@x{Q0);bB>3- z_?D75*#s+c*1NMid*WYT=P) zIn?#8?ph011jm}*u^Fz1AH#aMDah*fzOL?W>zTIZ>TCAh@TcY(34&I*t82S@bE>nB z{G;k)o%L;n z!_RX~TW6df41ytW=xYLt1I>0hyb9ljZ^DW?&M{|0o%`XVQTMI!TTe8qDs8rm8`~zM zdCA!b8Gr<7NJ zGu*J%4{bGV>9~=;p(9wG>zdzmUc}3euAyVnsd{83r$^DERm9RPi9uS{!%yOw z`{MkO^n8&YqMZC;{L&lC%SdO=d6U*+D2szV^6@XR5j}BW8n(ryShZF~WpG!}HH9&x ziZvG{5xio-x1>=X=`++f?(djkjbHHJ!XS#wr>K zPhk@8SZI72o;oh>(GcD6cZhEV+9EveyPzR!;X7$typL+?${oYVzG%=Fei~zZ=BagE z{C_>%615(iO~8A?BBYXuccP1XDR_O*3*JU^4sFK#9njM43GxwfH=AN`%}Gc~nOaq~ zT2=9PpNngkNfo3P?#cVOHE{-So$dh>Ssa>o&9lgF6>Z2Y`r&oCwkvxUiB_D&Dv4<) zuEF&&UP+|ikWE&8)QDxE!{-t*z zG1F**d_}wVQt;<9p$`N-p75)c<@uc9Jc&4~9-auZ(EpRgPi}R}Ud3Q#c_b_3n-|AY zCqWgYBX3C-S)P@pIZMjSw=^r-RD(9yQYu$neexb#*^67z>n!O-y(#7^pjOO7uBcKK zs*o#mzmzI+TQRcdAFD|rhjg+&M^^lns@t~MRotN^G6eH`|729=1z86JrEar>pRf%~zEb`Alne|0|Jr{%PR+`N1>tcGJHaA@< z+01R(8FB-!ReSn*A$J}R+G0*@S`G~hPY$Q64;rhf^ZMtG3a&+u`n4*})8T!t=LT98 zJ`Z|qBp-+jH~s-LytjG1;_jJ>L(6~9uPojPcPd-Gm{m_a?f%ERhWTY$f-G0@RJ7dB z^_P)j^7qH=UsbF3=i39!XI!@>V`Rhe#XbM(KABe>=XvL4Ep%6NP?4gSr@N9ftH96p zx4g@kKB47R>Q{6o*LJr3h`(IoPnQ!O zS!RU(PxhUp@?7x*i`=rMQL-g_@<^y?oloD59{k&-Zl6y%pC`Y6#q&T9ZQV`f(!3Ii z4I#5e*GG)wm(v4OpXkJ$#ud;J8w7H zhP=2<3_=BM`wUXdM#-Ux(Armwa7hblK2}1vtyrV1>9Ts|GO+_QQ~{W zY19~iC~sY^g=^}7-IH%kl_D{3pC~pNpN~x2@tXo~3+vB);Dyc1PE8 zZ}7b;apbDzNNsOmN3&3+$^Y7e??V8BRP!=B)JzN-_LwJH9V=#~+3zL5K^de6dXjs= z1HXh2*BN(9H8oCWR&!z= + public interface IFieldCollection : IReadOnlyCollectionWithId { } } diff --git a/src/Qwiq.Core/IFieldDefinitionCollection.cs b/src/Qwiq.Core/IFieldDefinitionCollection.cs index caa5403f..e9b95121 100644 --- a/src/Qwiq.Core/IFieldDefinitionCollection.cs +++ b/src/Qwiq.Core/IFieldDefinitionCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IFieldDefinitionCollection : IReadOnlyListWithId, + public interface IFieldDefinitionCollection : IReadOnlyCollectionWithId, IEquatable { } diff --git a/src/Qwiq.Core/INodeCollection.cs b/src/Qwiq.Core/INodeCollection.cs index 5f56429a..ea1ba51e 100644 --- a/src/Qwiq.Core/INodeCollection.cs +++ b/src/Qwiq.Core/INodeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface INodeCollection : IReadOnlyListWithId, IEquatable + public interface INodeCollection : IReadOnlyCollectionWithId, IEquatable { } } \ No newline at end of file diff --git a/src/Qwiq.Core/IProjectCollection.cs b/src/Qwiq.Core/IProjectCollection.cs index 7f7fa76d..0ef4e645 100644 --- a/src/Qwiq.Core/IProjectCollection.cs +++ b/src/Qwiq.Core/IProjectCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IProjectCollection : IReadOnlyList + public interface IProjectCollection : IReadOnlyCollection { IProject this[Guid id] { get; } } diff --git a/src/Qwiq.Core/IProjectProperty.cs b/src/Qwiq.Core/IProjectProperty.cs index 0c064f0b..c03c3c13 100644 --- a/src/Qwiq.Core/IProjectProperty.cs +++ b/src/Qwiq.Core/IProjectProperty.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Qwiq { public interface IProjectProperty diff --git a/src/Qwiq.Core/IReadOnlyList.cs b/src/Qwiq.Core/IReadOnlyCollection.cs similarity index 74% rename from src/Qwiq.Core/IReadOnlyList.cs rename to src/Qwiq.Core/IReadOnlyCollection.cs index 0ec98ac0..e18ed8db 100644 --- a/src/Qwiq.Core/IReadOnlyList.cs +++ b/src/Qwiq.Core/IReadOnlyCollection.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public interface IReadOnlyList : System.Collections.Generic.IReadOnlyList + public interface IReadOnlyCollection : System.Collections.Generic.IReadOnlyList { T this[string name] { get; } diff --git a/src/Qwiq.Core/IReadOnlyListWithId.cs b/src/Qwiq.Core/IReadOnlyCollectionWithId.cs similarity index 60% rename from src/Qwiq.Core/IReadOnlyListWithId.cs rename to src/Qwiq.Core/IReadOnlyCollectionWithId.cs index d11356a4..d370c117 100644 --- a/src/Qwiq.Core/IReadOnlyListWithId.cs +++ b/src/Qwiq.Core/IReadOnlyCollectionWithId.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IReadOnlyListWithId : IReadOnlyList, IEquatable> + public interface IReadOnlyCollectionWithId : IReadOnlyCollection, IEquatable> where T : IIdentifiable { T GetById(TId id); diff --git a/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs b/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs index e4ee1577..a28270b7 100644 --- a/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IRegisteredLinkTypeCollection : IReadOnlyList, IEquatable + public interface IRegisteredLinkTypeCollection : IReadOnlyCollection, IEquatable { } } \ No newline at end of file diff --git a/src/Qwiq.Core/IRelatedLink.cs b/src/Qwiq.Core/IRelatedLink.cs index 0cd1ea5a..7c2acf87 100644 --- a/src/Qwiq.Core/IRelatedLink.cs +++ b/src/Qwiq.Core/IRelatedLink.cs @@ -4,6 +4,7 @@ namespace Microsoft.Qwiq { public interface IRelatedLink : ILink, IEquatable { + [Obsolete("This property is deprecated and will be removed in a future release. Use LinkTypeEnd.Name instead.")] string LinkSubType { get; } IWorkItemLinkTypeEnd LinkTypeEnd { get; } diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.cs b/src/Qwiq.Core/ITeamFoundationIdentity.cs index 79631daf..f0762564 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.cs @@ -72,7 +72,7 @@ public interface ITeamFoundationIdentity /// /// /// If the current user is active (e.g. not deleted), the value is equal to - /// . + /// . /// /// The unique user identifier. int UniqueUserId { get; } diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index 2ffb19ff..78067e63 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -1,12 +1,18 @@ -using Microsoft.Qwiq.Credentials; using System; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq { - public interface ITfsTeamProjectCollection + [Obsolete("This interface is deprecated and will be removed in a future version. Use ITeamProjectCollection instead.")] + public interface ITfsTeamProjectCollection : ITeamProjectCollection + { + } + + public interface ITeamProjectCollection { /// Gets the credentials for this project collection. - TfsCredentials AuthorizedCredentials { get; } + VssCredentials AuthorizedCredentials { get; } /// /// The identity who the calls to the server are being made for. diff --git a/src/Qwiq.Core/IWorkItem.Extensions.cs b/src/Qwiq.Core/IWorkItem.Extensions.cs new file mode 100644 index 00000000..1ffbcbb5 --- /dev/null +++ b/src/Qwiq.Core/IWorkItem.Extensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Microsoft.Qwiq +{ + public static partial class Extensions + { + public static void AddRelatedLink(this IWorkItem workItem, IWorkItemStore store, int[] targets) + { + if (workItem == null) throw new ArgumentNullException(nameof(workItem)); + if (store == null) throw new ArgumentNullException(nameof(store)); + if (targets.Length == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(targets)); + + var end = store.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related].ForwardEnd; + + foreach (var id in targets) workItem.Links.Add(workItem.CreateRelatedLink(id, end)); + } + + + + + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 32457208..84ef1863 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -21,7 +21,7 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable IFieldCollection Fields { get; } - new int HyperLinkCount { get; } + new int HyperlinkCount { get; } /// /// Gets the ID of this work item. @@ -57,7 +57,7 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable /// Gets a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType object /// that represents the type of this work item. /// - /// + /// /// The Type property is null. /// IWorkItemType Type { get; } diff --git a/src/Qwiq.Core/IWorkItemCommon.cs b/src/Qwiq.Core/IWorkItemCommon.cs index 75be2d4d..fda655a0 100644 --- a/src/Qwiq.Core/IWorkItemCommon.cs +++ b/src/Qwiq.Core/IWorkItemCommon.cs @@ -55,7 +55,7 @@ public interface IWorkItemCommon : IWorkItemCore /// /// Gets the number of hyperlinks in this work item. /// - int? HyperLinkCount { get; } + int? HyperlinkCount { get; } int? IterationId { get; set; } diff --git a/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs b/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs index d531697e..608f15b5 100644 --- a/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IWorkItemLinkTypeCollection : IReadOnlyList, + public interface IWorkItemLinkTypeCollection : IReadOnlyCollection, IEquatable { IWorkItemLinkTypeEndCollection LinkTypeEnds { get; } diff --git a/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs index b6de5575..7382c30f 100644 --- a/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public interface IWorkItemLinkTypeEndCollection : IReadOnlyListWithId + public interface IWorkItemLinkTypeEndCollection : IReadOnlyCollectionWithId { } diff --git a/src/Qwiq.Core/IWorkItemStore.Extensions.cs b/src/Qwiq.Core/IWorkItemStore.Extensions.cs new file mode 100644 index 00000000..3273b0be --- /dev/null +++ b/src/Qwiq.Core/IWorkItemStore.Extensions.cs @@ -0,0 +1,26 @@ +using System; + +namespace Microsoft.Qwiq +{ + public static partial class Extensions + { + public static IWorkItemLinkTypeEnd GetChildLinkTypeEnd(this IWorkItemStore store) + { + if (store == null) throw new ArgumentNullException(nameof(store)); + return store.GetLinkType(CoreLinkTypeReferenceNames.Hierarchy).ReverseEnd; + } + + public static IWorkItemLinkType GetLinkType(this IWorkItemStore store, string linkTypeReferenceName) + { + if (store == null) throw new ArgumentNullException(nameof(store)); + return store.WorkItemLinkTypes[linkTypeReferenceName]; + } + + public static IWorkItemLinkTypeEnd GetParentLinkTypeEnd(this IWorkItemStore store) + { + return store.GetChildLinkTypeEnd().OppositeEnd; + } + + + } +} diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 46ebe7c8..9ca4a9b3 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -1,19 +1,19 @@ using System; using System.Collections.Generic; -using Microsoft.Qwiq.Credentials; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq { /// /// Represents the work item store resource. /// - /// + /// public interface IWorkItemStore : IDisposable { /// Gets the credentials for this project collection. /// The authorized credentials. - TfsCredentials AuthorizedCredentials { get; } + VssCredentials AuthorizedCredentials { get; } /// /// Indicates the communication type used for the work item store. @@ -38,7 +38,7 @@ public interface IWorkItemStore : IDisposable /// Gets the team project collection. /// /// The team project collection. - ITfsTeamProjectCollection TeamProjectCollection { get; } + ITeamProjectCollection TeamProjectCollection { get; } /// /// Gets the time zone. diff --git a/src/Qwiq.Core/IWorkItemType.Extensions.cs b/src/Qwiq.Core/IWorkItemType.Extensions.cs new file mode 100644 index 00000000..ea6df1e2 --- /dev/null +++ b/src/Qwiq.Core/IWorkItemType.Extensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq +{ + public static partial class Extensions + { + public static IWorkItem NewWorkItem(this IWorkItemType wit, IEnumerable> values) + { + if (wit == null) throw new ArgumentNullException(nameof(wit)); + var wi = wit.NewWorkItem(); + + if (values == null) + { + return wi; + } + foreach (var kvp in values) wi[kvp.Key] = kvp.Value; + + return wi; + } + + public static IEnumerable NewWorkItems( + this IWorkItemType wit, + IEnumerable>> values) + { + return values.Select(wit.NewWorkItem); + } + } +} diff --git a/src/Qwiq.Core/IWorkItemTypeCollection.cs b/src/Qwiq.Core/IWorkItemTypeCollection.cs index 2d212d89..3e19a069 100644 --- a/src/Qwiq.Core/IWorkItemTypeCollection.cs +++ b/src/Qwiq.Core/IWorkItemTypeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IWorkItemTypeCollection : IReadOnlyList, IEquatable + public interface IWorkItemTypeCollection : IReadOnlyCollection, IEquatable { } } \ No newline at end of file diff --git a/src/Qwiq.Core/IdentifiableComparer.cs b/src/Qwiq.Core/IdentifiableComparer.cs index ea3c40ec..0d9036b5 100644 --- a/src/Qwiq.Core/IdentifiableComparer.cs +++ b/src/Qwiq.Core/IdentifiableComparer.cs @@ -2,11 +2,11 @@ { public class IdentifiableComparer : GenericComparer> { - public static IdentifiableComparer Instance => Nested.Instance; + internal new static IdentifiableComparer Default => Nested.Instance; private IdentifiableComparer() { - + } public override bool Equals(IIdentifiable x, IIdentifiable y) @@ -20,22 +20,19 @@ public override bool Equals(IIdentifiable x, IIdentifiable y) public override int GetHashCode(IIdentifiable obj) { - unchecked - { - var hash = 27; + if (ReferenceEquals(obj, null)) return 0; - hash = (13 * hash) ^ obj.Id.GetHashCode(); - - return hash; - } + return 351 ^ obj.Id.GetHashCode(); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested { internal static readonly IdentifiableComparer Instance = new IdentifiableComparer(); // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/IdentityDescriptor.cs b/src/Qwiq.Core/IdentityDescriptor.cs index 2d35baf4..ec80dab0 100644 --- a/src/Qwiq.Core/IdentityDescriptor.cs +++ b/src/Qwiq.Core/IdentityDescriptor.cs @@ -1,12 +1,10 @@ -using System; - -using Microsoft.VisualStudio.Services.Identity; +using Microsoft.VisualStudio.Services.Identity; +using System; namespace Microsoft.Qwiq { - public class IdentityDescriptor : IIdentityDescriptor, IComparable + public class IdentityDescriptor : IIdentityDescriptor, IComparable, IEquatable { - protected byte _identityType; private string _identifier; /// @@ -42,41 +40,51 @@ private set public string IdentityType { - get => IdentityTypeMapper.Instance.GetTypeNameFromId(_identityType); + get => IdentityTypeMapper.Instance.GetTypeNameFromId(IdentityTypeId); private set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); if (value.Length > 128) throw new ArgumentOutOfRangeException(nameof(value)); - _identityType = IdentityTypeMapper.Instance.GetTypeIdFromName(value); + IdentityTypeId = IdentityTypeMapper.Instance.GetTypeIdFromName(value); } } + protected internal byte IdentityTypeId { get; set; } + public int CompareTo(IdentityDescriptor other) { - if (this == other) - return 0; - if (this == null && other != null) - return -1; - if (this != null && other == null) - return 1; - int num = 0; - if (_identityType > other._identityType) - num = 1; - else if (_identityType < other._identityType) - num = -1; - if (num == 0) - num = StringComparer.OrdinalIgnoreCase.Compare(Identifier, other.Identifier); + if (this == other) return 0; + if (this == null && other != null) return -1; + if (this != null && other == null) return 1; + + var num = 0; + if (IdentityTypeId > other.IdentityTypeId) num = 1; + else if (IdentityTypeId < other.IdentityTypeId) num = -1; + + if (num == 0) num = StringComparer.OrdinalIgnoreCase.Compare(Identifier, other.Identifier); return num; } - public override string ToString() + public override int GetHashCode() { - return IdentityTypeMapper.Instance.GetTypeNameFromId(_identityType) + ";" + _identifier; + return IdentityTypeId ^ StringComparer.OrdinalIgnoreCase.GetHashCode(Identifier); } - public override int GetHashCode() + /// + public bool Equals(IdentityDescriptor other) + { + return CompareTo(other) == 0; + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as IdentityDescriptor); + } + + public override string ToString() { - return _identityType ^ StringComparer.OrdinalIgnoreCase.GetHashCode(Identifier); + return IdentityTypeMapper.Instance.GetTypeNameFromId(IdentityTypeId) + ";" + _identifier; } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/IdentityDescriptorComparer.cs b/src/Qwiq.Core/IdentityDescriptorComparer.cs index 37b7e4f9..2b97ca8a 100644 --- a/src/Qwiq.Core/IdentityDescriptorComparer.cs +++ b/src/Qwiq.Core/IdentityDescriptorComparer.cs @@ -1,24 +1,16 @@ -using System; - namespace Microsoft.Qwiq { public class IdentityDescriptorComparer : GenericComparer { - public static IdentityDescriptorComparer Instance => Nested.Instance; + internal new static IdentityDescriptorComparer Default => Nested.Instance; - public override bool Equals(IIdentityDescriptor x, IIdentityDescriptor y) + private IdentityDescriptorComparer() { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - return string.Equals(x.Identifier, y.Identifier, StringComparison.OrdinalIgnoreCase) - && string.Equals(x.IdentityType, y.IdentityType, StringComparison.OrdinalIgnoreCase); } - - // ReSharper disable ClassNeverInstantiated.Local + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested // ReSharper restore ClassNeverInstantiated.Local { @@ -28,6 +20,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index 46d1d112..e582656e 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.Services.Common; using System; +using System.Globalization; namespace Microsoft.Qwiq { @@ -15,6 +16,7 @@ public class IdentityFieldValue public IdentityFieldValue(ITeamFoundationIdentity identity) : this(identity.DisplayName, identity.Descriptor.Identifier, identity.TeamFoundationId.ToString()) { + if (identity == null) throw new ArgumentNullException(nameof(identity)); } /// @@ -35,22 +37,16 @@ public IdentityFieldValue(string displayName, string fullName, string sid) var arr = FullName.Split(IdentityConstants.DomainAccountNameSeparator); if (arr.Length != 2 || arr[1] == Sid) return; - if (arr[1].Contains("@")) { Email = arr[1]; Alias = arr[1].Split('@')[0]; - Guid guid; - if (Guid.TryParse(arr[0], out guid)) - { - Domain = arr[0]; - } + if (Guid.TryParse(arr[0], out Guid guid)) Domain = arr[0]; } else { - Guid guid; - if (Guid.TryParse(arr[0], out guid)) + if (Guid.TryParse(arr[0], out Guid guid)) { Alias = arr[1]; } @@ -60,21 +56,20 @@ public IdentityFieldValue(string displayName, string fullName, string sid) Alias = arr[1]; } } - - } public IdentityFieldValue(string displayName) { - if (displayName.Contains("<")) - { - var arr = displayName.Split('<'); - if (arr[1].Contains("@")) + if (!string.IsNullOrEmpty(displayName)) + if (displayName.Contains("<")) { - Email = arr[1].Trim('>'); - Alias = Email.Split('@')[0]; + var arr = displayName.Split('<'); + if (arr[1].Contains("@")) + { + Email = arr[1].Trim('>'); + Alias = Email.Split('@')[0]; + } } - } DisplayPart = displayName; } @@ -89,9 +84,7 @@ public IdentityFieldValue(string displayName) /// Gets the display name. /// /// The display name without the account name, if it exists. - public string DisplayName => !string.IsNullOrEmpty(DisplayPart) - ? DisplayPart.Split('<')[0].Trim() - : DisplayPart; + public string DisplayName => !string.IsNullOrEmpty(DisplayPart) ? DisplayPart.Split('<')[0].Trim() : DisplayPart; /// /// Gets the display part. @@ -129,8 +122,7 @@ public string IdentityName get { if (!string.IsNullOrEmpty(Email)) return Email; - if (!string.IsNullOrEmpty(Domain)) - return string.Format(IdentityConstants.DomainQualifiedAccountNameFormat, Domain, Alias); + if (!string.IsNullOrEmpty(Domain)) return string.Format(CultureInfo.InvariantCulture, IdentityConstants.DomainQualifiedAccountNameFormat, Domain, Alias); if (!string.IsNullOrEmpty(Alias)) return Alias; return string.Empty; diff --git a/src/Qwiq.Core/Node.cs b/src/Qwiq.Core/Node.cs index 4e1de5f2..ad2aad58 100644 --- a/src/Qwiq.Core/Node.cs +++ b/src/Qwiq.Core/Node.cs @@ -64,19 +64,19 @@ internal Node(int id, bool isAreaNode, bool isIterationNode, string name, Uri ur [DebuggerStepThrough] public bool Equals(INode other) { - return NodeComparer.Instance.Equals(this, other); + return NodeComparer.Default.Equals(this, other); } [DebuggerStepThrough] public override bool Equals(object obj) { - return NodeComparer.Instance.Equals(this, obj as INode); + return NodeComparer.Default.Equals(this, obj as INode); } [DebuggerStepThrough] public override int GetHashCode() { - return NodeComparer.Instance.GetHashCode(this); + return NodeComparer.Default.GetHashCode(this); } [DebuggerStepThrough] diff --git a/src/Qwiq.Core/NodeCollection.cs b/src/Qwiq.Core/NodeCollection.cs index be2fc410..6a9439fe 100644 --- a/src/Qwiq.Core/NodeCollection.cs +++ b/src/Qwiq.Core/NodeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class NodeCollection : ReadOnlyListWithId, INodeCollection + public class NodeCollection : ReadOnlyCollectionWithId, INodeCollection { internal NodeCollection(params INode[] nodes) : this(nodes as IEnumerable) @@ -16,17 +16,17 @@ internal NodeCollection(IEnumerable nodes) public bool Equals(INodeCollection other) { - return Comparer.NodeCollectionComparer.Equals(this, other); + return Comparer.NodeCollection.Equals(this, other); } public override bool Equals(object obj) { - return Comparer.NodeCollectionComparer.Equals(this, obj as INodeCollection); + return Comparer.NodeCollection.Equals(this, obj as INodeCollection); } public override int GetHashCode() { - return Comparer.NodeCollectionComparer.GetHashCode(this); + return Comparer.NodeCollection.GetHashCode(this); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/NodeComparer.cs b/src/Qwiq.Core/NodeComparer.cs index 4a773439..53efd0f1 100644 --- a/src/Qwiq.Core/NodeComparer.cs +++ b/src/Qwiq.Core/NodeComparer.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { public class NodeComparer : GenericComparer { - public static readonly NodeComparer Instance = Nested.Instance; + internal new static readonly NodeComparer Default = Nested.Instance; private NodeComparer() { @@ -50,6 +50,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/NullableIdentifiableComparer.cs b/src/Qwiq.Core/NullableIdentifiableComparer.cs index 64de001d..a501c928 100644 --- a/src/Qwiq.Core/NullableIdentifiableComparer.cs +++ b/src/Qwiq.Core/NullableIdentifiableComparer.cs @@ -2,7 +2,7 @@ { public class NullableIdentifiableComparer : GenericComparer> { - public static NullableIdentifiableComparer Instance => Nested.Instance; + internal new static NullableIdentifiableComparer Default => Nested.Instance; private NullableIdentifiableComparer() { @@ -15,27 +15,24 @@ public override bool Equals(IIdentifiable x, IIdentifiable y) if (ReferenceEquals(x, null)) return false; if (ReferenceEquals(y, null)) return false; - return x.Id == y.Id; + return GenericComparer.Default.Equals(x.Id, y.Id); } public override int GetHashCode(IIdentifiable obj) { - unchecked - { - var hash = 27; - - hash = (13 * hash) ^ (obj.Id.HasValue ? obj.Id.GetHashCode() : 0); + if (ReferenceEquals(obj, null)) return 0; - return hash; - } + return 351 ^ (obj.Id.HasValue ? obj.Id.GetHashCode() : 0); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested { internal static readonly NullableIdentifiableComparer Instance = new NullableIdentifiableComparer(); // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/Project.cs b/src/Qwiq.Core/Project.cs index ebf097a3..7fce91ba 100644 --- a/src/Qwiq.Core/Project.cs +++ b/src/Qwiq.Core/Project.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Microsoft.Qwiq { @@ -32,7 +33,7 @@ private Project() public bool Equals(IProject other) { - return ProjectComparer.Instance.Equals(this, other); + return ProjectComparer.Default.Equals(this, other); } public INodeCollection AreaRootNodes => _area.Value; @@ -49,17 +50,18 @@ public bool Equals(IProject other) public override bool Equals(object obj) { - return ProjectComparer.Instance.Equals(this, obj as IProject); + return ProjectComparer.Default.Equals(this, obj as IProject); } public override int GetHashCode() { - return ProjectComparer.Instance.GetHashCode(this); + return ProjectComparer.Default.GetHashCode(this); } public override string ToString() { - return $"{Guid} ({Name})"; + FormattableString s = $"{Guid} ({Name})"; + return s.ToString(CultureInfo.InvariantCulture); } public Guid Id => Guid; diff --git a/src/Qwiq.Core/ProjectCollection.cs b/src/Qwiq.Core/ProjectCollection.cs index bfefb98c..98c4d37e 100644 --- a/src/Qwiq.Core/ProjectCollection.cs +++ b/src/Qwiq.Core/ProjectCollection.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - internal class ProjectCollection : ReadOnlyListWithId, IProjectCollection + internal class ProjectCollection : ReadOnlyCollectionWithId, IProjectCollection { internal ProjectCollection(params IProject[] projects) : this(projects as IEnumerable) diff --git a/src/Qwiq.Core/ProjectComparer.cs b/src/Qwiq.Core/ProjectComparer.cs index dfdc73b1..38a1d19b 100644 --- a/src/Qwiq.Core/ProjectComparer.cs +++ b/src/Qwiq.Core/ProjectComparer.cs @@ -1,11 +1,10 @@ using System; -using System.Linq; namespace Microsoft.Qwiq { public class ProjectComparer : GenericComparer { - public static ProjectComparer Instance => Nested.Instance; + internal new static ProjectComparer Default => Nested.Instance; public override bool Equals(IProject x, IProject y) { @@ -17,7 +16,7 @@ public override bool Equals(IProject x, IProject y) && x.Guid.Equals(y.Guid) && Equals(x.AreaRootNodes, y.AreaRootNodes) && Equals(x.IterationRootNodes, y.IterationRootNodes) - && WorkItemTypeCollectionComparer.Instance.Equals(x.WorkItemTypes, y.WorkItemTypes); + && WorkItemTypeCollectionComparer.Default.Equals(x.WorkItemTypes, y.WorkItemTypes); } public override int GetHashCode(IProject obj) @@ -29,14 +28,15 @@ public override int GetHashCode(IProject obj) var hash = 27; hash = (13 * hash) ^ obj.Guid.GetHashCode(); hash = (13 * hash) ^ (obj.Name != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name) : 0); - hash = (13 * hash) ^ Comparer.NodeCollectionComparer.GetHashCode(obj.AreaRootNodes); - hash = (13 * hash) ^ Comparer.NodeCollectionComparer.GetHashCode(obj.IterationRootNodes); - hash = (13 * hash) ^ WorkItemTypeCollectionComparer.Instance.GetHashCode(obj.WorkItemTypes); + hash = (13 * hash) ^ Comparer.NodeCollection.GetHashCode(obj.AreaRootNodes); + hash = (13 * hash) ^ Comparer.NodeCollection.GetHashCode(obj.IterationRootNodes); + hash = (13 * hash) ^ WorkItemTypeCollectionComparer.Default.GetHashCode(obj.WorkItemTypes); return hash; } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested { // ReSharper disable MemberHidesStaticFromOuterClass @@ -45,6 +45,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/Properties/AssemblyInfo.cs b/src/Qwiq.Core/Properties/AssemblyInfo.cs index 317272f9..84b0c9f7 100644 --- a/src/Qwiq.Core/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ +using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -6,7 +7,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Core")] -[assembly: AssemblyDescription("Provides Quick Workitem Queries to TFS and Visual Studio Online")] +[assembly: AssemblyDescription("Provides Quick Work Item Queries to Team Foundation Server and Visual Studio Online")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("Microsoft.Qwiq")] @@ -22,6 +23,8 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("1f6293cb-bcca-4038-a696-4358d285b986")] +[assembly: CLSCompliant(true)] + [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index b9731e7f..cd46cbb1 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -12,8 +12,16 @@ Microsoft.Qwiq.Core v4.6 512 + true ..\ true + prompt + 4 + + ..\..\build\rulesets\ship.ruleset + true + true + 1591 @@ -23,21 +31,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - CS0108 - ..\..\build\rulesets\ship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true @@ -87,6 +87,7 @@ + @@ -94,25 +95,27 @@ - + - - + + + + @@ -120,9 +123,9 @@ - + @@ -152,21 +155,20 @@ + + - - - - ITeamFoundationIdentity.cs - + + @@ -176,22 +178,23 @@ + + - - - - + + + @@ -207,7 +210,6 @@ - @@ -228,11 +230,8 @@ Designer - - - diff --git a/src/Qwiq.Core/ReadOnlyList.cs b/src/Qwiq.Core/ReadOnlyCollection.cs similarity index 91% rename from src/Qwiq.Core/ReadOnlyList.cs rename to src/Qwiq.Core/ReadOnlyCollection.cs index cdf29aed..c98c6bec 100644 --- a/src/Qwiq.Core/ReadOnlyList.cs +++ b/src/Qwiq.Core/ReadOnlyCollection.cs @@ -10,7 +10,7 @@ namespace Microsoft.Qwiq /// Base class for common operations for Collections. /// /// - public abstract class ReadOnlyList : IReadOnlyList + public abstract class ReadOnlyCollection : IReadOnlyCollection { private readonly object _lockObj = new object(); @@ -24,7 +24,7 @@ public abstract class ReadOnlyList : IReadOnlyList private IDictionary _mapByName; - protected ReadOnlyList(Func> itemFactory, Func nameFunc) + protected ReadOnlyCollection(Func> itemFactory, Func nameFunc) { if (itemFactory == null) throw new ArgumentNullException(nameof(itemFactory)); if (nameFunc == null) throw new ArgumentNullException(nameof(nameFunc)); @@ -32,12 +32,12 @@ protected ReadOnlyList(Func> itemFactory, Func nameFun _nameFunc = nameFunc; } - protected ReadOnlyList(IEnumerable items, Func nameFunc) + protected ReadOnlyCollection(IEnumerable items, Func nameFunc) : this(() => items ?? Enumerable.Empty(), nameFunc) { } - protected ReadOnlyList() + protected ReadOnlyCollection() { Initialize(); } @@ -83,8 +83,7 @@ public virtual T this[string name] { if (name == null) throw new ArgumentNullException(nameof(name)); Ensure(); - int num; - if (_mapByName.TryGetValue(name, out num)) return List[num]; + if (_mapByName.TryGetValue(name, out int num)) return List[num]; throw new DeniedOrNotExistException(); } @@ -121,8 +120,7 @@ public virtual int IndexOf(T value) public virtual bool TryGetByName(string name, out T value) { Ensure(); - int num; - if (_mapByName.TryGetValue(name, out num)) + if (_mapByName.TryGetValue(name, out int num)) { value = List[num]; return true; @@ -137,7 +135,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - T IReadOnlyList.GetItem(int index) + T IReadOnlyCollection.GetItem(int index) { return GetItem(index); } diff --git a/src/Qwiq.Core/ReadOnlyListWithId.cs b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs similarity index 69% rename from src/Qwiq.Core/ReadOnlyListWithId.cs rename to src/Qwiq.Core/ReadOnlyCollectionWithId.cs index 21337ffb..caedf7a9 100644 --- a/src/Qwiq.Core/ReadOnlyListWithId.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs @@ -3,18 +3,18 @@ namespace Microsoft.Qwiq { - public abstract class ReadOnlyListWithId : ReadOnlyList, IReadOnlyListWithId + public abstract class ReadOnlyCollectionWithId : ReadOnlyCollection, IReadOnlyCollectionWithId where T : IIdentifiable { private readonly Func _idFunc; private readonly IDictionary _mapById; - protected ReadOnlyListWithId(IEnumerable items, Func nameFunc) + protected ReadOnlyCollectionWithId(IEnumerable items, Func nameFunc) :this(items, nameFunc, arg => arg.Id) { } - protected ReadOnlyListWithId(IEnumerable items, Func nameFunc, Func idFunc) + protected ReadOnlyCollectionWithId(IEnumerable items, Func nameFunc, Func idFunc) :base(items, nameFunc) { _idFunc = idFunc ?? throw new ArgumentNullException(nameof(idFunc)); @@ -30,8 +30,7 @@ public virtual bool Contains(TId id) public virtual bool TryGetById(TId id, out T value) { Ensure(); - int index; - if (_mapById.TryGetValue(id, out index)) + if (_mapById.TryGetValue(id, out int index)) { value = this[index]; return true; @@ -66,19 +65,19 @@ protected void AddById(TId id, int index) } } - public virtual bool Equals(IReadOnlyListWithId other) + public virtual bool Equals(IReadOnlyCollectionWithId other) { - return ReadOnlyListWithIdComparer.Default.Equals(this, other); + return ReadOnlyCollectionWithIdComparer.Default.Equals(this, other); } public override bool Equals(object obj) { - return ReadOnlyListWithIdComparer.Default.Equals(this, obj as IReadOnlyListWithId); + return ReadOnlyCollectionWithIdComparer.Default.Equals(this, obj as IReadOnlyCollectionWithId); } public override int GetHashCode() { - return ReadOnlyListWithIdComparer.Default.GetHashCode(this); + return ReadOnlyCollectionWithIdComparer.Default.GetHashCode(this); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/ReadOnlyListWithIdComparer.cs b/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs similarity index 62% rename from src/Qwiq.Core/ReadOnlyListWithIdComparer.cs rename to src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs index 017c25de..fb6b47ce 100644 --- a/src/Qwiq.Core/ReadOnlyListWithIdComparer.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs @@ -2,12 +2,12 @@ namespace Microsoft.Qwiq { - public class ReadOnlyListWithIdComparer : GenericComparer> + public class ReadOnlyCollectionWithIdComparer : GenericComparer> where T : IIdentifiable { - public new static readonly ReadOnlyListWithIdComparer Default = new ReadOnlyListWithIdComparer(); + public new static readonly ReadOnlyCollectionWithIdComparer Default = new ReadOnlyCollectionWithIdComparer(); - public override bool Equals(IReadOnlyListWithId x, IReadOnlyListWithId y) + public override bool Equals(IReadOnlyCollectionWithId x, IReadOnlyCollectionWithId y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; @@ -31,7 +31,7 @@ public override bool Equals(IReadOnlyListWithId x, IReadOnlyListWithId obj) + public override int GetHashCode(IReadOnlyCollectionWithId obj) { if (ReferenceEquals(obj, null)) return 0; @@ -40,10 +40,4 @@ public override int GetHashCode(IReadOnlyListWithId obj) .Aggregate(27, (current, node) => (13 * current) ^ GenericComparer.Default.GetHashCode(node)); } } - - public static class Comparer - { - public static readonly ReadOnlyListWithIdComparer NodeCollectionComparer = ReadOnlyListWithIdComparer.Default; - public static readonly ReadOnlyListWithIdComparer FieldCollectionComparer = ReadOnlyListWithIdComparer.Default; - } } \ No newline at end of file diff --git a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs index 4f8a7526..89920a09 100644 --- a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public class RegisteredLinkTypeCollection : ReadOnlyList, IRegisteredLinkTypeCollection + public class RegisteredLinkTypeCollection : ReadOnlyCollection, IRegisteredLinkTypeCollection { public RegisteredLinkTypeCollection(IEnumerable linkTypes) : base(linkTypes, type => type.Name) diff --git a/src/Qwiq.Core/RelatedLink.cs b/src/Qwiq.Core/RelatedLink.cs index 898cb512..9c723355 100644 --- a/src/Qwiq.Core/RelatedLink.cs +++ b/src/Qwiq.Core/RelatedLink.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Microsoft.Qwiq { @@ -12,6 +13,7 @@ internal RelatedLink(int related, IWorkItemLinkTypeEnd linkTypeEnd = null, strin LinkTypeEnd = linkTypeEnd; } + [Obsolete("This property is deprecated and will be removed in a future release. Use LinkTypeEnd.Name instead.")] public string LinkSubType => LinkTypeEnd?.Name; public IWorkItemLinkTypeEnd LinkTypeEnd { get; } @@ -31,7 +33,7 @@ public bool Equals(IRelatedLink other) if (ReferenceEquals(other, null)) return false; return RelatedWorkItemId.Equals(other.RelatedWorkItemId) - && WorkItemLinkTypeEndComparer.Instance.Equals(LinkTypeEnd, other.LinkTypeEnd); + && WorkItemLinkTypeEndComparer.Default.Equals(LinkTypeEnd, other.LinkTypeEnd); } /// @@ -41,7 +43,7 @@ public override int GetHashCode() { var hash = 27; hash = (13 * hash) ^ RelatedWorkItemId.GetHashCode(); - hash = (13 * hash) ^ (LinkTypeEnd != null ? WorkItemLinkTypeEndComparer.Instance.GetHashCode(LinkTypeEnd) : 0); + hash = (13 * hash) ^ (LinkTypeEnd != null ? WorkItemLinkTypeEndComparer.Default.GetHashCode(LinkTypeEnd) : 0); return hash; } diff --git a/src/Qwiq.Core/Revision.cs b/src/Qwiq.Core/Revision.cs index f43517d7..5757fb0b 100644 --- a/src/Qwiq.Core/Revision.cs +++ b/src/Qwiq.Core/Revision.cs @@ -7,27 +7,33 @@ internal class Revision : IRevision { private readonly Lazy _fields; - private Dictionary _values; - internal Revision( IFieldDefinitionCollection definitions, int revision, Func fieldFactory) { Rev = revision; - _values = new Dictionary(); + new Dictionary(); _fields = new Lazy(() => fieldFactory(this, definitions)); } - internal Revision( - WorkItem workItem, - int revision) + internal Revision(WorkItem workItem, int revision) { WorkItem = workItem ?? throw new ArgumentNullException(nameof(workItem)); Rev = revision; _fields = new Lazy(() => WorkItem.Fields); } + public IFieldCollection Fields => _fields.Value; + + public int? Id => WorkItem?.Id; + + public int? Rev { get; } + + public string Url => WorkItem?.Url; + + private WorkItem WorkItem { get; } + public virtual object this[string name] { get @@ -37,21 +43,10 @@ public virtual object this[string name] } } - public int? Rev { get; } - object IWorkItemCore.this[string name] { get => this[name]; set => throw new NotSupportedException(); } - - private WorkItem WorkItem { get; } - - public IFieldCollection Fields => _fields.Value; - - - public int? Id => WorkItem?.Id; - - public string Url => WorkItem?.Url; } } \ No newline at end of file diff --git a/src/Qwiq.Core/TeamFoundationIdentity.cs b/src/Qwiq.Core/TeamFoundationIdentity.cs index e9a06fc5..d182c65d 100644 --- a/src/Qwiq.Core/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core/TeamFoundationIdentity.cs @@ -21,7 +21,7 @@ int uniqueUserId public bool Equals(ITeamFoundationIdentity other) { - return TeamFoundationIdentityComparer.Instance.Equals(this, other); + return TeamFoundationIdentityComparer.Default.Equals(this, other); } public abstract IIdentityDescriptor Descriptor { get; } @@ -97,7 +97,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return TeamFoundationIdentityComparer.Instance.GetHashCode(this); + return TeamFoundationIdentityComparer.Default.GetHashCode(this); } public override string ToString() diff --git a/src/Qwiq.Core/TeamFoundationIdentityComparer.cs b/src/Qwiq.Core/TeamFoundationIdentityComparer.cs index d7fa79d6..e7ebb67a 100644 --- a/src/Qwiq.Core/TeamFoundationIdentityComparer.cs +++ b/src/Qwiq.Core/TeamFoundationIdentityComparer.cs @@ -4,13 +4,13 @@ namespace Microsoft.Qwiq { public class TeamFoundationIdentityComparer : GenericComparer { - public static TeamFoundationIdentityComparer Instance => Nested.Instance; + internal new static TeamFoundationIdentityComparer Default => Nested.Instance; public override int GetHashCode(ITeamFoundationIdentity obj) { if (ReferenceEquals(obj, null)) return 0; - return IdentityDescriptorComparer.Instance.GetHashCode(obj.Descriptor); + return IdentityDescriptorComparer.Default.GetHashCode(obj.Descriptor); } public override bool Equals(ITeamFoundationIdentity x, ITeamFoundationIdentity y) @@ -20,7 +20,7 @@ public override bool Equals(ITeamFoundationIdentity x, ITeamFoundationIdentity y if (ReferenceEquals(y, null)) return false; return StringComparer.OrdinalIgnoreCase.Equals(x.UniqueName, y.UniqueName) - && IdentityDescriptorComparer.Instance.Equals(x.Descriptor, y.Descriptor); + && IdentityDescriptorComparer.Default.Equals(x.Descriptor, y.Descriptor); } // ReSharper disable ClassNeverInstantiated.Local @@ -33,6 +33,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/TypeParser.cs b/src/Qwiq.Core/TypeParser.cs index 1c75b603..557588d9 100644 --- a/src/Qwiq.Core/TypeParser.cs +++ b/src/Qwiq.Core/TypeParser.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Qwiq { @@ -8,18 +9,15 @@ namespace Microsoft.Qwiq /// public class TypeParser : ITypeParser { - public static ITypeParser Default => Nested.Instance; - private TypeParser() { } + public static ITypeParser Default => Nested.Instance; + public object Parse(Type destinationType, object value, object defaultValue) { - return ParseImpl( - destinationType, - value, - new Lazy(() => defaultValue ?? GetDefaultValueOfType(destinationType))); + return ParseImpl(destinationType, value, new Lazy(() => defaultValue ?? GetDefaultValueOfType(destinationType))); } public object Parse(Type destinationType, object input) @@ -44,8 +42,7 @@ private static object GetDefaultValueOfType(Type type) private static bool IsGenericNullable(Type type) { - return type.IsGenericType && type.GetGenericTypeDefinition() - == typeof(Nullable<>).GetGenericTypeDefinition(); + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } private static object ParseImpl(Type destinationType, object value, Lazy defaultValueFactory) @@ -55,15 +52,9 @@ private static object ParseImpl(Type destinationType, object value, Lazy // Quit if no type conversion is actually required if (value.GetType() == destinationType) return value; - if (destinationType.IsInstanceOfType(value)) return value; - - object result; - - if (TryConvert(destinationType, value, out result)) return result; - + if (TryConvert(destinationType, value, out object result)) return result; if (IsGenericNullable(destinationType) && defaultValueFactory.Value == null) return null; - if (TryConvert(destinationType, defaultValueFactory.Value, out result)) return result; return null; @@ -72,7 +63,6 @@ private static object ParseImpl(Type destinationType, object value, Lazy private static bool TryConvert(Type destinationType, object value, out object result) { if (IsGenericNullable(destinationType)) - { try { var converter = new NullableConverter(destinationType); @@ -81,10 +71,9 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } - } var valueType = value.GetType(); var typeConverter = TypeDescriptor.GetConverter(valueType); @@ -96,7 +85,7 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } @@ -109,7 +98,7 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } @@ -117,9 +106,7 @@ private static bool TryConvert(Type destinationType, object value, out object re { var val = value.ToString(); if (!string.IsNullOrEmpty(val)) - { if (typeConverter.IsValid(val)) - { try { result = typeConverter.ConvertFromString(val); @@ -127,11 +114,9 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable EmptyGeneralCatchClause catch - // ReSharper restore EmptyGeneralCatchClause + // ReSharper restore EmptyGeneralCatchClause { } - } - } } result = null; @@ -145,7 +130,7 @@ private static bool ValueRepresentsNull(object value) // ReSharper disable ClassNeverInstantiated.Local private class Nested - // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore ClassNeverInstantiated.Local { // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly ITypeParser Instance = new TypeParser(); @@ -154,6 +139,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index f5e6bd37..fbbb11f1 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -37,7 +37,7 @@ protected internal WorkItem(IWorkItemType type, Func fieldColl public bool Equals(IWorkItem other) { - return WorkItemComparer.Instance.Equals(this, other); + return WorkItemComparer.Default.Equals(this, other); } public new virtual int AttachedFileCount => base.AttachedFileCount.GetValueOrDefault(0); @@ -55,7 +55,7 @@ public bool Equals(IWorkItem other) public int Index => -2; - public new virtual int HyperLinkCount => base.HyperLinkCount.GetValueOrDefault(0); + public new virtual int HyperlinkCount => base.HyperlinkCount.GetValueOrDefault(0); public new virtual int Id => base.Id.GetValueOrDefault(0); @@ -186,12 +186,12 @@ public virtual IEnumerable Validate() public override bool Equals(object obj) { - return WorkItemComparer.Instance.Equals(this, obj as IWorkItem); + return WorkItemComparer.Default.Equals(this, obj as IWorkItem); } public override int GetHashCode() { - return WorkItemComparer.Instance.GetHashCode(this); + return WorkItemComparer.Default.GetHashCode(this); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index 69b63c7e..e9f4e6a5 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -57,7 +57,7 @@ public virtual string History set => SetValue(CoreFieldRefNames.History, value); } - public virtual int? HyperLinkCount => GetValue(CoreFieldRefNames.HyperLinkCount); + public virtual int? HyperlinkCount => GetValue(CoreFieldRefNames.HyperlinkCount); public virtual int? IterationId { @@ -101,7 +101,7 @@ public virtual string Title public bool Equals(IWorkItemCommon other) { - return NullableIdentifiableComparer.Instance.Equals(this, other); + return NullableIdentifiableComparer.Default.Equals(this, other); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemComparer.cs b/src/Qwiq.Core/WorkItemComparer.cs index 931ce70b..03de8ce8 100644 --- a/src/Qwiq.Core/WorkItemComparer.cs +++ b/src/Qwiq.Core/WorkItemComparer.cs @@ -6,7 +6,7 @@ private WorkItemComparer() { } - public static WorkItemComparer Instance => Nested.Instance; + internal new static WorkItemComparer Default => Nested.Instance; public override bool Equals(IWorkItem x, IWorkItem y) { @@ -14,12 +14,12 @@ public override bool Equals(IWorkItem x, IWorkItem y) if (ReferenceEquals(x, null)) return false; if (ReferenceEquals(y, null)) return false; - return IdentifiableComparer.Instance.Equals(x, y); + return Comparer.Identifiable.Equals(x, y); } public override int GetHashCode(IWorkItem obj) { - return IdentifiableComparer.Instance.GetHashCode(obj); + return IdentifiableComparer.Default.GetHashCode(obj); } private class Nested diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index faedf8b9..2bdf1094 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -24,14 +24,14 @@ protected internal WorkItemCore(IDictionary fields) public abstract string Url { get; } /// - /// Gets or sets the with the specified name. + /// Gets or sets the with the specified name. /// /// - /// The . + /// The . /// /// The name. /// - /// + /// /// name is null /// public virtual object this[string name] @@ -61,34 +61,33 @@ protected virtual object GetValue(string name) protected virtual void SetValue(string name, object value) { - if (_fields != null) - { - _fields[name] = value; - } + _fields[name] = value; } public bool Equals(IWorkItemCore other) { - return NullableIdentifiableComparer.Instance.Equals(this, other); + return NullableIdentifiableComparer.Default.Equals(this, other); } public override bool Equals(object obj) { - return NullableIdentifiableComparer.Instance.Equals(this, obj as IWorkItemCore); + return NullableIdentifiableComparer.Default.Equals(this, obj as IWorkItemCore); } public override int GetHashCode() { - return NullableIdentifiableComparer.Instance.GetHashCode(this); + return NullableIdentifiableComparer.Default.GetHashCode(this); } public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) { + if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); return GetValue(fieldDefinition.ReferenceName); } public void SetFieldValue(IFieldDefinition fieldDefinition, object value) { + if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); SetValue(fieldDefinition.ReferenceName, value); } } diff --git a/src/Qwiq.Core/WorkItemExtensions.cs b/src/Qwiq.Core/WorkItemExtensions.cs deleted file mode 100644 index cf2fcb51..00000000 --- a/src/Qwiq.Core/WorkItemExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Qwiq -{ - public static class WorkItemExtensions - { - public static void AddRelatedLink(this IWorkItem workItem, IWorkItemStore store, int[] targets) - { - var end = store.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related].ForwardEnd; - - foreach (var id in targets) workItem.Links.Add(workItem.CreateRelatedLink(id, end)); - } - - public static IWorkItemLinkTypeEnd GetChildLinkTypeEnd(this IWorkItemStore store) - { - return store.GetLinkType(CoreLinkTypeReferenceNames.Hierarchy).ReverseEnd; - } - - public static IWorkItemLinkType GetLinkType(this IWorkItemStore store, string linkTypeReferenceName) - { - return store.WorkItemLinkTypes[linkTypeReferenceName]; - } - - public static IWorkItemLinkTypeEnd GetParentLinkTypeEnd(this IWorkItemStore store) - { - return store.GetChildLinkTypeEnd().OppositeEnd; - } - - public static IWorkItem NewWorkItem(this IWorkItemType wit, IEnumerable> values) - { - var wi = wit.NewWorkItem(); - - if (values != null) foreach (var kvp in values) wi[kvp.Key] = kvp.Value; - - return wi; - } - - public static IEnumerable NewWorkItems( - this IWorkItemType wit, - IEnumerable>> values) - { - return values.Select(wit.NewWorkItem); - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkInfo.cs b/src/Qwiq.Core/WorkItemLinkInfo.cs index 94fe07b8..d89f4e31 100644 --- a/src/Qwiq.Core/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core/WorkItemLinkInfo.cs @@ -31,31 +31,31 @@ internal WorkItemLinkInfo(int sourceId, int targetId, Lazy [DebuggerStepThrough] public bool Equals(IWorkItemLinkInfo other) { - return WorkItemLinkInfoComparer.Instance.Equals(this, other); + return WorkItemLinkInfoComparer.Default.Equals(this, other); } [DebuggerStepThrough] public static bool operator !=(WorkItemLinkInfo x, WorkItemLinkInfo y) { - return !WorkItemLinkInfoComparer.Instance.Equals(x, y); + return !WorkItemLinkInfoComparer.Default.Equals(x, y); } [DebuggerStepThrough] public static bool operator ==(WorkItemLinkInfo x, WorkItemLinkInfo y) { - return WorkItemLinkInfoComparer.Instance.Equals(x, y); + return WorkItemLinkInfoComparer.Default.Equals(x, y); } [DebuggerStepThrough] public override bool Equals(object obj) { - return WorkItemLinkInfoComparer.Instance.Equals(this, obj as IWorkItemLinkInfo); + return WorkItemLinkInfoComparer.Default.Equals(this, obj as IWorkItemLinkInfo); } [DebuggerStepThrough] public override int GetHashCode() { - return WorkItemLinkInfoComparer.Instance.GetHashCode(this); + return WorkItemLinkInfoComparer.Default.GetHashCode(this); } public override string ToString() diff --git a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs index 9aa64cf8..ce9773ae 100644 --- a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { public class WorkItemLinkInfoComparer : GenericComparer { - public static WorkItemLinkInfoComparer Instance => Nested.Instance; + internal new static WorkItemLinkInfoComparer Default => Nested.Instance; public override bool Equals(IWorkItemLinkInfo x, IWorkItemLinkInfo y) { @@ -16,6 +16,8 @@ public override bool Equals(IWorkItemLinkInfo x, IWorkItemLinkInfo y) public override int GetHashCode(IWorkItemLinkInfo obj) { + if (ReferenceEquals(obj, null)) return 0; + unchecked { var hash = 27; @@ -27,6 +29,7 @@ public override int GetHashCode(IWorkItemLinkInfo obj) } // ReSharper disable ClassNeverInstantiated.Local + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested // ReSharper restore ClassNeverInstantiated.Local { diff --git a/src/Qwiq.Core/WorkItemLinkType.cs b/src/Qwiq.Core/WorkItemLinkType.cs index 20eaa033..2c503fec 100644 --- a/src/Qwiq.Core/WorkItemLinkType.cs +++ b/src/Qwiq.Core/WorkItemLinkType.cs @@ -8,15 +8,17 @@ public class WorkItemLinkType : IWorkItemLinkType, IEquatable private readonly Lazy _reverseFac; - protected IWorkItemLinkTypeEnd _forward; + private IWorkItemLinkTypeEnd _forward; - protected IWorkItemLinkTypeEnd _reverse; + private IWorkItemLinkTypeEnd _reverse; internal WorkItemLinkType(string referenceName, IWorkItemLinkTypeEnd forward, IWorkItemLinkTypeEnd reverse) : this(referenceName) { _forward = forward ?? throw new ArgumentNullException(nameof(forward)); _reverse = reverse ?? throw new ArgumentNullException(nameof(reverse)); + _forwardFac = null; + _reverseFac = null; } internal WorkItemLinkType(string referenceName, Lazy forward, Lazy reverse) @@ -33,7 +35,7 @@ internal WorkItemLinkType(string referenceName) throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); } - public IWorkItemLinkTypeEnd ForwardEnd => _forward ?? _forwardFac.Value; + public IWorkItemLinkTypeEnd ForwardEnd => CoerceForwardValue(); public bool IsActive { get; internal set; } @@ -41,26 +43,48 @@ internal WorkItemLinkType(string referenceName) public string ReferenceName { get; } - public IWorkItemLinkTypeEnd ReverseEnd => _reverse ?? _reverseFac.Value; + public IWorkItemLinkTypeEnd ReverseEnd => CoerceReverseValue(); public override bool Equals(object obj) { - return WorkItemLinkTypeComparer.Instance.Equals(this, obj as IWorkItemLinkType); + return WorkItemLinkTypeComparer.Default.Equals(this, obj as IWorkItemLinkType); } public bool Equals(IWorkItemLinkType other) { - return WorkItemLinkTypeComparer.Instance.Equals(this, other); + return WorkItemLinkTypeComparer.Default.Equals(this, other); } public override int GetHashCode() { - return WorkItemLinkTypeComparer.Instance.GetHashCode(this); + return WorkItemLinkTypeComparer.Default.GetHashCode(this); } public override string ToString() { return ReferenceName; } + + internal void SetReverseEnd(IWorkItemLinkTypeEnd value) + { + if (_reverse != null) throw new InvalidOperationException($"{nameof(ReverseEnd)} already contains a value."); + _reverse = value ?? throw new ArgumentNullException(nameof(value)); + } + + internal void SetForwardEnd(IWorkItemLinkTypeEnd value) + { + if (_forward != null) throw new InvalidOperationException($"{nameof(ForwardEnd)} already contains a value."); + _forward = value ?? throw new ArgumentNullException(nameof(value)); + } + + private IWorkItemLinkTypeEnd CoerceForwardValue() + { + return _forward ?? (_forward = _forwardFac.Value); + } + + private IWorkItemLinkTypeEnd CoerceReverseValue() + { + return _reverse ?? (_reverse = _reverseFac.Value); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs index abb6f1b2..aae99977 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeCollection : ReadOnlyList, IWorkItemLinkTypeCollection + public class WorkItemLinkTypeCollection : ReadOnlyCollection, IWorkItemLinkTypeCollection { private readonly Lazy _ltCol; @@ -34,7 +34,7 @@ public override bool Equals(object obj) var ltc = obj as IEnumerable; if (ltc == null) return false; - return this.All(p => ltc.Contains(p, WorkItemLinkTypeComparer.Instance)); + return this.All(p => ltc.Contains(p, WorkItemLinkTypeComparer.Default)); } public override int GetHashCode() diff --git a/src/Qwiq.Core/WorkItemLinkTypeComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs index 4b21073b..f89e9cc4 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs @@ -4,7 +4,11 @@ namespace Microsoft.Qwiq { public class WorkItemLinkTypeComparer : GenericComparer { - public static WorkItemLinkTypeComparer Instance => Nested.Instance; + internal new static WorkItemLinkTypeComparer Default => Nested.Instance; + + private WorkItemLinkTypeComparer() + { + } public override bool Equals(IWorkItemLinkType x, IWorkItemLinkType y) { @@ -15,20 +19,22 @@ public override bool Equals(IWorkItemLinkType x, IWorkItemLinkType y) return x.IsActive == y.IsActive && x.IsDirectional == y.IsDirectional && string.Equals(x.ReferenceName, y.ReferenceName, StringComparison.OrdinalIgnoreCase) - && WorkItemLinkTypeEndComparer.Instance.Equals(x.ForwardEnd, y.ForwardEnd) - && WorkItemLinkTypeEndComparer.Instance.Equals(x.ReverseEnd, y.ReverseEnd); + && WorkItemLinkTypeEndComparer.Default.Equals(x.ForwardEnd, y.ForwardEnd) + && WorkItemLinkTypeEndComparer.Default.Equals(x.ReverseEnd, y.ReverseEnd); } public override int GetHashCode(IWorkItemLinkType obj) { + if (ReferenceEquals(obj, null)) return 0; + unchecked { var hash = 27; - hash = (13 * hash) ^ (obj.ReferenceName != null ? obj.ReferenceName.GetHashCode() : 0); + hash = (13 * hash) ^ (obj.ReferenceName != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(obj.ReferenceName) : 0); hash = (13 * hash) ^ obj.IsActive.GetHashCode(); hash = (13 * hash) ^ obj.IsDirectional.GetHashCode(); - hash = (13 * hash) ^ (obj.ForwardEnd != null ? WorkItemLinkTypeEndComparer.Instance.GetHashCode(obj.ForwardEnd) : 0); - hash = (13 * hash) ^ (obj.ReverseEnd != null ? WorkItemLinkTypeEndComparer.Instance.GetHashCode(obj.ReverseEnd) : 0); + hash = (13 * hash) ^ (obj.ForwardEnd != null ? WorkItemLinkTypeEndComparer.Default.GetHashCode(obj.ForwardEnd) : 0); + hash = (13 * hash) ^ (obj.ReverseEnd != null ? WorkItemLinkTypeEndComparer.Default.GetHashCode(obj.ReverseEnd) : 0); return hash; } @@ -40,6 +46,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs index 1d9d5cfd..1d259424 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs @@ -2,9 +2,8 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeEnd : IWorkItemLinkTypeEnd, - IEquatable, - IComparable + public class WorkItemLinkTypeEnd : IWorkItemLinkTypeEnd, IEquatable + { private readonly Lazy _opposite; @@ -19,18 +18,7 @@ internal WorkItemLinkTypeEnd(string immutableName) ImmutableName = immutableName; if (string.IsNullOrWhiteSpace(immutableName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(immutableName)); - _opposite = new Lazy( - () => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); - } - - public int CompareTo(IWorkItemLinkTypeEnd other) - { - return WorkItemLinkTypeEndComparer.Instance.Compare(this, other); - } - - public bool Equals(IWorkItemLinkTypeEnd other) - { - return WorkItemLinkTypeEndComparer.Instance.Equals(this, other); + _opposite = new Lazy(() => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); } public int Id { get; internal set; } @@ -45,14 +33,19 @@ public bool Equals(IWorkItemLinkTypeEnd other) public IWorkItemLinkTypeEnd OppositeEnd => _opposite.Value; + public bool Equals(IWorkItemLinkTypeEnd other) + { + return WorkItemLinkTypeEndComparer.Default.Equals(this, other); + } + public override bool Equals(object obj) { - return WorkItemLinkTypeEndComparer.Instance.Equals(this, obj as IWorkItemLinkTypeEnd); + return WorkItemLinkTypeEndComparer.Default.Equals(this, obj as IWorkItemLinkTypeEnd); } public override int GetHashCode() { - return WorkItemLinkTypeEndComparer.Instance.GetHashCode(this); + return WorkItemLinkTypeEndComparer.Default.GetHashCode(this); } public override string ToString() diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs index 2fd64855..6db886b5 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeEndCollection : ReadOnlyListWithId, + public class WorkItemLinkTypeEndCollection : ReadOnlyCollectionWithId, IWorkItemLinkTypeEndCollection { internal WorkItemLinkTypeEndCollection(IEnumerable linkTypes) diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs index f4dedb4a..f8f9edfd 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { public class WorkItemLinkTypeEndComparer : GenericComparer { - public static WorkItemLinkTypeEndComparer Instance => Nested.Instance; + internal new static WorkItemLinkTypeEndComparer Default => Nested.Instance; public override bool Equals(IWorkItemLinkTypeEnd x, IWorkItemLinkTypeEnd y) { @@ -33,6 +33,7 @@ public override int GetHashCode(IWorkItemLinkTypeEnd obj) } // ReSharper disable ClassNeverInstantiated.Local + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested // ReSharper restore ClassNeverInstantiated.Local { @@ -42,6 +43,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/WorkItemType.cs b/src/Qwiq.Core/WorkItemType.cs index b8da2db9..6235472f 100644 --- a/src/Qwiq.Core/WorkItemType.cs +++ b/src/Qwiq.Core/WorkItemType.cs @@ -26,7 +26,7 @@ internal WorkItemType( public bool Equals(IWorkItemType other) { - return WorkItemTypeComparer.Instance.Equals(this, other); + return WorkItemTypeComparer.Default.Equals(this, other); } public string Description { get; } @@ -42,12 +42,12 @@ public IWorkItem NewWorkItem() public override bool Equals(object obj) { - return WorkItemTypeComparer.Instance.Equals(this, obj as IWorkItemType); + return WorkItemTypeComparer.Default.Equals(this, obj as IWorkItemType); } public override int GetHashCode() { - return WorkItemTypeComparer.Instance.GetHashCode(this); + return WorkItemTypeComparer.Default.GetHashCode(this); } public override string ToString() diff --git a/src/Qwiq.Core/WorkItemTypeCollection.cs b/src/Qwiq.Core/WorkItemTypeCollection.cs index 685d39b6..39c29a80 100644 --- a/src/Qwiq.Core/WorkItemTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemTypeCollection.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - public class WorkItemTypeCollection : ReadOnlyList, IWorkItemTypeCollection + public class WorkItemTypeCollection : ReadOnlyCollection, IWorkItemTypeCollection { [DebuggerStepThrough] internal WorkItemTypeCollection(Func> workItemTypesFactory) @@ -27,19 +27,19 @@ internal WorkItemTypeCollection(IEnumerable workItemTypes) [DebuggerStepThrough] public bool Equals(IWorkItemTypeCollection other) { - return WorkItemTypeCollectionComparer.Instance.Equals(this, other); + return WorkItemTypeCollectionComparer.Default.Equals(this, other); } [DebuggerStepThrough] public override bool Equals(object obj) { - return WorkItemTypeCollectionComparer.Instance.Equals(this, obj as IWorkItemTypeCollection); + return WorkItemTypeCollectionComparer.Default.Equals(this, obj as IWorkItemTypeCollection); } [DebuggerStepThrough] public override int GetHashCode() { - return WorkItemTypeCollectionComparer.Instance.GetHashCode(this); + return WorkItemTypeCollectionComparer.Default.GetHashCode(this); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs b/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs index 90e67681..f78a41ac 100644 --- a/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { public class WorkItemTypeCollectionComparer : GenericComparer { - public static WorkItemTypeCollectionComparer Instance => Nested.Instance; + internal new static WorkItemTypeCollectionComparer Default => Nested.Instance; public override bool Equals(IWorkItemTypeCollection x, IWorkItemTypeCollection y) { @@ -20,7 +20,7 @@ public override bool Equals(IWorkItemTypeCollection x, IWorkItemTypeCollection y { if (!y.Contains(wit.Name)) return false; var tw = y[wit.Name]; - if (!WorkItemTypeComparer.Instance.Equals(wit, tw)) return false; + if (!WorkItemTypeComparer.Default.Equals(wit, tw)) return false; // Removes the first occurrence, so if there are duplicates we'll still get a valid mismatch source.Remove(wit); @@ -43,6 +43,7 @@ public override int GetHashCode(IWorkItemTypeCollection obj) } // ReSharper disable ClassNeverInstantiated.Local + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] private class Nested // ReSharper restore ClassNeverInstantiated.Local { @@ -53,6 +54,7 @@ private class Nested // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { } diff --git a/src/Qwiq.Core/WorkItemTypeComparer.cs b/src/Qwiq.Core/WorkItemTypeComparer.cs index c270e68b..89aca3a4 100644 --- a/src/Qwiq.Core/WorkItemTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeComparer.cs @@ -4,7 +4,11 @@ namespace Microsoft.Qwiq { public class WorkItemTypeComparer : GenericComparer { - public static WorkItemTypeComparer Instance => Nested.Instance; + internal new static WorkItemTypeComparer Default => Nested.Instance; + + private WorkItemTypeComparer() + { + } public override bool Equals(IWorkItemType x, IWorkItemType y) { @@ -14,18 +18,20 @@ public override bool Equals(IWorkItemType x, IWorkItemType y) return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(x.Description, y.Description, StringComparison.OrdinalIgnoreCase) - && FieldDefinitionCollectionComparer.Instance.Equals(x.FieldDefinitions, y.FieldDefinitions); + && FieldDefinitionCollectionComparer.Default.Equals(x.FieldDefinitions, y.FieldDefinitions); } public override int GetHashCode(IWorkItemType obj) { - // Disable overflow compiler check + if (ReferenceEquals(obj, null)) return 0; + + unchecked { var hash = 27; hash = (13 * hash) ^ (obj.Name != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name) : 0); hash = (13 * hash) ^ (obj.Description != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Description) : 0); - hash = (13 * hash) ^ (obj.FieldDefinitions != null ? obj.FieldDefinitions.GetHashCode() : 0); + hash = (13 * hash) ^ (obj.FieldDefinitions != null ? Comparer.FieldDefinitionCollection.GetHashCode(obj.FieldDefinitions) : 0); return hash; } diff --git a/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs index 469cfcd3..d074de1c 100644 --- a/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs @@ -21,9 +21,9 @@ public BulkIdentityAwareAttributeMapperStrategy(IPropertyInspector inspector, II _identityManagementService = identityManagementService; } - public override void Map(Type targeWorkItemType, IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) + public override void Map(Type targeWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { - var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Instance); + var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Default); if (!workingSet.Any()) return; var validIdentityProperties = GetWorkItemIdentityFieldNameToIdentityPropertyMap(targeWorkItemType, _inspector); @@ -54,7 +54,7 @@ internal static IDictionary GetIdentityMap(IIdentityManagementSe return ims.GetAliasesForDisplayNames(searchTerms.ToArray()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value.FirstOrDefault()); } - internal static ICollection GetWorkItemsWithIdentityFieldValues(IEnumerable workItems, IReadOnlyCollection witFieldNames) + internal static ICollection GetWorkItemsWithIdentityFieldValues(IEnumerable workItems, System.Collections.Generic.IReadOnlyCollection witFieldNames) { return workItems.Select( diff --git a/src/Qwiq.Identity/Mapper/IdentifiableComparer.cs b/src/Qwiq.Identity/Mapper/IdentifiableComparer.cs deleted file mode 100644 index 870fb17f..00000000 --- a/src/Qwiq.Identity/Mapper/IdentifiableComparer.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; - -using Microsoft.Qwiq.Mapper; - -namespace Microsoft.Qwiq.Identity.Mapper -{ - - - internal class IdentifiableComparer : GenericComparer - { - private IdentifiableComparer() - { - - } - - public static IdentifiableComparer Instance => Nested.Instance; - - public override bool Equals(IIdentifiable x, IIdentifiable y) - { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - - return x.Id == y.Id; - } - - public override int GetHashCode(IIdentifiable obj) - { - unchecked - { - - - var hash = 27; - - hash = (13 * hash) ^ obj.Id.GetHashCode(); - - return hash; - } - } - - private class Nested - { - internal static readonly IdentifiableComparer Instance = new IdentifiableComparer(); - - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static Nested() - { - } - } - } -} diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 2be27fa9..292b9d23 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -12,8 +12,16 @@ Microsoft.Qwiq.Identity v4.6 512 + true ..\ true + prompt + 4 + CS0108 + ..\..\build\rulesets\ship.ruleset + true + true + 1591 @@ -24,10 +32,7 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true + false AnyCPU @@ -35,10 +40,6 @@ true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true @@ -50,7 +51,6 @@ - diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index eeb84722..6820da91 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -12,8 +12,16 @@ Microsoft.Qwiq.Linq v4.6 512 + true ..\ true + prompt + 4 + CS0108 + ..\..\build\rulesets\ship.ruleset + true + true + 1591 @@ -23,20 +31,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true diff --git a/src/Qwiq.Linq/SimpleFieldMapper.cs b/src/Qwiq.Linq/SimpleFieldMapper.cs index 0a2a6e24..2312c324 100644 --- a/src/Qwiq.Linq/SimpleFieldMapper.cs +++ b/src/Qwiq.Linq/SimpleFieldMapper.cs @@ -32,8 +32,7 @@ public IEnumerable GetFieldNames(Type type) public string GetFieldName(Type type, string propertyName) { - string name; - return Mappings.TryGetValue(propertyName, out name) ? name : propertyName; + return Mappings.TryGetValue(propertyName, out string name) ? name : propertyName; } } } \ No newline at end of file diff --git a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs index fc0b35a3..ef2e9aaa 100644 --- a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs @@ -66,7 +66,7 @@ public override void Map(IEnumerable> workItemMapp } } - public override void Map(Type targetWorkItemType, IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) + public override void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { var accessor = TypeAccessor.Create(targetWorkItemType, true); diff --git a/src/Qwiq.Mapper/Attributes/PropertyInspector.cs b/src/Qwiq.Mapper/Attributes/PropertyInspector.cs index 140fad26..ec92999d 100644 --- a/src/Qwiq.Mapper/Attributes/PropertyInspector.cs +++ b/src/Qwiq.Mapper/Attributes/PropertyInspector.cs @@ -28,11 +28,9 @@ public T GetAttribute(PropertyInfo property) where T : Attribute private static IEnumerable AnnotatedPropertiesCache(IPropertyReflector reflector, Type workItemType, Type attributeType) { - ConcurrentDictionary> f; - if (AnnotatedProperties.TryGetValue(workItemType.TypeHandle, out f)) + if (AnnotatedProperties.TryGetValue(workItemType.TypeHandle, out ConcurrentDictionary> f)) { - IEnumerable pis; - if (f.TryGetValue(attributeType.TypeHandle, out pis)) + if (f.TryGetValue(attributeType.TypeHandle, out IEnumerable pis)) { return pis; } diff --git a/src/Qwiq.Mapper/Attributes/PropertyReflector.cs b/src/Qwiq.Mapper/Attributes/PropertyReflector.cs index 7ee51202..ddac0585 100644 --- a/src/Qwiq.Mapper/Attributes/PropertyReflector.cs +++ b/src/Qwiq.Mapper/Attributes/PropertyReflector.cs @@ -35,8 +35,7 @@ public override Attribute GetAttribute(Type type, PropertyInfo property) private static IEnumerable TypePropertiesCache(Type type) { - IEnumerable pis; - if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) + if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable pis)) { return pis; } @@ -48,8 +47,7 @@ private static IEnumerable TypePropertiesCache(Type type) private static IEnumerable CustomAttributesCache(PropertyInfo property) { - IEnumerable @as; - if (PropertyAttributes.TryGetValue(property, out @as)) + if (PropertyAttributes.TryGetValue(property, out IEnumerable @as)) { return @as; } diff --git a/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs index f03774aa..1b00c476 100644 --- a/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs @@ -58,10 +58,7 @@ protected virtual IEnumerable Query(IEnumerable ids) return Store.Query(ids); } - public override void Map( - Type targetWorkItemType, - IEnumerable> workItemMappings, - IWorkItemMapper workItemMapper) + public override void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { var linksLookup = BuildLinksRelationships(targetWorkItemType, workItemMappings); @@ -81,7 +78,7 @@ public override void Map( // REVIEW: The recursion of links can cause mapping multiple times on the same values // For example, a common ancestor - var previouslyMapped = new Dictionary, IIdentifiable>(); + var previouslyMapped = new Dictionary, IIdentifiable>(); // Enumerate through items requiring a VSO lookup and map the objects // There are n-passes to map, where n=number of link types @@ -107,9 +104,8 @@ public override void Map( var propertyType = def.WorkItemType; var linkType = def.LinkName; var key = new Tuple(sourceWorkItem.Id, linkType); - List linkIds; - if (!linksLookup.TryGetValue(key, out linkIds)) + if (!linksLookup.TryGetValue(key, out List linkIds)) { // Could not find any IDs for the given ID/LinkType continue; @@ -122,8 +118,7 @@ public override void Map( .Select( s => { - IWorkItem val; - workItems.TryGetValue(s, out val); + workItems.TryGetValue(s, out IWorkItem val); return val; }).Where(p => p != null) .ToList(); @@ -167,7 +162,9 @@ public override void Map( } } - private Dictionary, List> BuildLinksRelationships(Type targetWorkItemType, IEnumerable> workItemMappings) + private Dictionary, List> BuildLinksRelationships( + Type targetWorkItemType, + IEnumerable>> workItemMappings) { var linksLookup = new Dictionary, List>(); diff --git a/src/Qwiq.Mapper/IWorkItemMapper.cs b/src/Qwiq.Mapper/IWorkItemMapper.cs index 8705a641..65a59448 100644 --- a/src/Qwiq.Mapper/IWorkItemMapper.cs +++ b/src/Qwiq.Mapper/IWorkItemMapper.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; namespace Microsoft.Qwiq.Mapper @@ -16,9 +15,9 @@ public interface IWorkItemMapper /// The subclass of Issue that the work items should become /// The set of TFS WorkItems to convert /// A cloned set of -subclassed items. - IEnumerable Create(IEnumerable collection) where T : IIdentifiable, new(); + IEnumerable Create(IEnumerable collection) where T : IIdentifiable, new(); - IEnumerable Create(Type type, IEnumerable collection); + IEnumerable> Create(Type type, IEnumerable collection); /// /// Create a new, empty work item sub-class. diff --git a/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs b/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs index f3b11138..0c135a22 100644 --- a/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs +++ b/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Mapper { public interface IWorkItemMapperStrategy { - void Map(Type targetWorkItemType, IEnumerable> workItemMappings, IWorkItemMapper workItemMapper); - void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) where T : IIdentifiable, new(); + void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper); + void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) where T : IIdentifiable, new(); } } diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index f1fd5e7e..4bf28d7b 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -12,8 +12,16 @@ Microsoft.Qwiq.Mapper v4.6 512 + true ..\ true + prompt + 4 + CS0108 + ..\..\build\rulesets\ship.ruleset + true + true + 1591 @@ -23,20 +31,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true diff --git a/src/Qwiq.Mapper/WorkItemMapper.cs b/src/Qwiq.Mapper/WorkItemMapper.cs index d29d1f4f..57c92449 100644 --- a/src/Qwiq.Mapper/WorkItemMapper.cs +++ b/src/Qwiq.Mapper/WorkItemMapper.cs @@ -10,7 +10,7 @@ public class WorkItemMapper : IWorkItemMapper { public IEnumerable MapperStrategies { get; } - private delegate IIdentifiable ObjectActivator(); + private delegate IIdentifiable ObjectActivator(); private static readonly ConcurrentDictionary OptimizedCtorExpression = new ConcurrentDictionary(); public WorkItemMapper(IEnumerable mapperStrategies) @@ -23,7 +23,7 @@ public WorkItemMapper(IEnumerable mapperStrategies) return new T(); } - public IEnumerable Create(IEnumerable collection) where T : IIdentifiable, new() + public IEnumerable Create(IEnumerable collection) where T : IIdentifiable, new() { var workItemsToMap = new Dictionary(); foreach (var item in collection) @@ -39,12 +39,12 @@ public WorkItemMapper(IEnumerable mapperStrategies) return workItemsToMap.Select(wi => wi.Value); } - public IEnumerable Create(Type type, IEnumerable collection) + public IEnumerable> Create(Type type, IEnumerable collection) { // Activator.CreateInstance is about 0.2 ms per 1,000 // Compiled expression is about 0.04 ms per 1,000 var compiled = OptimizedCtorExpressionCache(type); - var workItemsToMap = new Dictionary(); + var workItemsToMap = new Dictionary>(); foreach (var item in collection) { workItemsToMap[item] = compiled.Invoke(); diff --git a/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs b/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs index 90fba7fa..8203c88b 100644 --- a/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs +++ b/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs @@ -8,7 +8,7 @@ public abstract class WorkItemMapperStrategyBase : IWorkItemMapperStrategy { public virtual void Map( Type targetWorkItemType, - IEnumerable> workItemMappings, + IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { foreach (var workItemMapping in workItemMappings) @@ -16,23 +16,24 @@ public virtual void Map( } public virtual void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) - where T : IIdentifiable, new() + where T : IIdentifiable, new() { - Map(typeof(T), workItemMappings.Select(s => new KeyValuePair(s.Key, s.Value)), workItemMapper); + Map(typeof(T), workItemMappings.Select(s => new KeyValuePair>(s.Key, s.Value)), workItemMapper); } protected virtual void Map( Type targetWorkItemType, IWorkItem sourceWorkItem, - IIdentifiable targetWorkItem, + IIdentifiable targetWorkItem, IWorkItemMapper workItemMapper) { - Map(targetWorkItemType, new[] { new KeyValuePair(sourceWorkItem, targetWorkItem) }, workItemMapper); + Map(targetWorkItemType, new[] { new KeyValuePair>(sourceWorkItem, targetWorkItem) }, workItemMapper); } protected virtual void Map(IWorkItem sourceWorkItem, T targetWorkItem, IWorkItemMapper workItemMapper) + where T : IIdentifiable, new() { - Map(typeof(T), sourceWorkItem, (IIdentifiable)targetWorkItem, workItemMapper); + Map(typeof(T), sourceWorkItem, targetWorkItem, workItemMapper); } } } \ No newline at end of file diff --git a/test/Qwiq.Benchmark/BenchmarkConfig.cs b/test/Qwiq.Benchmark/BenchmarkConfig.cs index fc0d4e5d..578405ba 100644 --- a/test/Qwiq.Benchmark/BenchmarkConfig.cs +++ b/test/Qwiq.Benchmark/BenchmarkConfig.cs @@ -4,7 +4,7 @@ using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; -namespace Qwiq.Benchmark +namespace Microsoft.Qwiq.Benchmark { public class BenchmarkConfig : ManualConfig { diff --git a/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs b/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs index 5f86fb1c..f60bbe5d 100644 --- a/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs +++ b/test/Qwiq.Benchmark/BenchmarkContextSpecification.cs @@ -3,7 +3,7 @@ using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Qwiq.Benchmark +namespace Microsoft.Qwiq.Benchmark { public abstract class BenchmarkContextSpecification : ContextSpecification { @@ -11,14 +11,12 @@ public abstract class BenchmarkContextSpecification : ContextSpecification public override void Given() { if (Debugger.IsAttached) - { Assert.Fail("Never should use an attached debugger (e.g. Visual Studio or WinDbg) during the benchmarking."); - } #if DEBUG - Assert.Fail("Never use the Debug build for benchmarking. Never. The debug version of the target method can run 10–100 times slower."); + Assert.Fail( + "Never use the Debug build for benchmarking. Never. The debug version of the target method can run 10–100 times slower."); #endif - } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Benchmark/Constants.cs b/test/Qwiq.Benchmark/Constants.cs index 0a5f95a5..9138ff30 100644 --- a/test/Qwiq.Benchmark/Constants.cs +++ b/test/Qwiq.Benchmark/Constants.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Qwiq.Benchmark +namespace Microsoft.Qwiq.Benchmark { public static class Constants { diff --git a/test/Qwiq.Benchmark/GlobalSuppressions.cs b/test/Qwiq.Benchmark/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..6a7558723d8975ce14758268d3fa835f4c1f3557 GIT binary patch literal 2182 zcmd5-O^eh(5PfIC|Ip;5u$x`ig$2RG=pqU#D;Wh557{KnMrV>SABuloeXlxgGLymc zFfg6&u8&t$ud4t2{)7}Qe+4$U#Tvix1P%$;7M-2OxJF5-6)MzrS|Z~b<2m;^Wfr_i zO}C|_ZW8p^^4IWI*53@PtQ2^Fh$phrn8g}j=qtusTG{ZtqEv(v;)v*Dfk(K)IWCFp zmHDeET~eo{t%lYU+|f=?1Ua7ZETQLuyEXrFL)jRTjCzXD+up?_%MMS?=R5qQ?eBwJ zLY%c(4EKt5L)$G+Q{EPXB9whhwV3wMAB1y{A>U zzy&LD&MGXdrhzf4!fp*-GQJ-?{lIn2Oy;ae$B1jItgo_kjx+xEMCN(AAN?gBTlAep zQ?g%reu;02&hz#)SDiH$`)*!tcqYCU2RV6+TsXdt3|TtE8GCHWZqmeYHVxG8ZrJyh zXZ7?gJKp`9RcSf zgeFt)EIA*ACT5MVj6<_3o_M&~xjD7jrXE|C0p@Z6Dq_hZ{D9ED32D7-03FA-468B-AD1A-249CEF9C6CDB} Library Properties - Qwiq.Benchmark - Qwiq.Benchmark + Microsoft.Qwiq.Benchmark + Microsoft.Qwiq.Benchmark v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + 512 + true + ..\ + true + prompt + 4 + + ..\..\build\rulesets\noship.ruleset + true + true + 1591 - true @@ -27,20 +36,12 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true @@ -131,25 +132,14 @@ ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll - - - - - - - - + - - - - @@ -171,28 +161,6 @@ - - - - - - - - - False - - - False - - - False - - - False - - - - diff --git a/test/Qwiq.Benchmark/TraceEvent.ReadMe.txt b/test/Qwiq.Benchmark/TraceEvent.ReadMe.txt deleted file mode 100644 index f37f579e..00000000 --- a/test/Qwiq.Benchmark/TraceEvent.ReadMe.txt +++ /dev/null @@ -1,79 +0,0 @@ - -************* Welcome to the Microsoft.Diagnostics.Tracing.TraceEvent library! *************** - -This library is designed to make controlling and parsing Event Tracing for Windows (ETW) events easy. -In particular if you are generating events with System.Diagnostics.Tracing.EventSource, this library -makes it easy to process that data. - -******** PROGRAMMERS GUIDE ******** - -If you are new to TraceEvent, see the _TraceEventProgammersGuide.docx that was installed as part of -your solution when this NuGet package was installed. - -************ FEEDBACK ************* - -If you have problems, wish to report a bug, or have a suggestion please log your comments on the -.NET Runtime Framework Blog http://blogs.msdn.com/b/dotnet/ under the TraceEvent announcement. - -********** RELEASE NOTES *********** - -If you are interested what particular features/bug fixes are in this particular version please -see the TraceEvent.RelaseNotes.txt file that is part of this package. It also contains -information about breaking changes (If you use the bcl.codeplex version in the past). - -************* SAMPLES ************* - -There is a companion NUGET package called Microsoft.Diagnostics.Tracing.TraceEvent.Samples. These -are simple but well commented examples of how to use this library. To get the samples, it is best -to simply create a new Console application, and then reference the Samples package from that App. -The package's README.TXT file tell you how to run the samples. - -************** BLOGS ************** - -See http://blogs.msdn.com/b/vancem/archive/tags/traceevent/ for useful blog entries on using this -package. - -*********** QUICK STARTS *********** - -The quick-starts below will get you going in a minimum of typing, but please see the WELL COMMENTED -samples in the Samples NUGET package that describe important background and other common scenarios. - -************************************************************************************************** -******* Quick Start: Turning on the 'MyEventSource' EventSource and log to MyEventsFile.etl: - - using (var session = new TraceEventSession("SimpleMontitorSession", "MyEventsFile.etl")) // Sessions collect and control event providers. Here we send data to a file - { - var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("MyEventSource"); // Get the unique ID for the eventSouce. - session.EnableProvider(eventSourceGuid); // Turn it on. - Thread.Sleep(10000); // Collect for 10 seconds then stop. - } - -************************************************************************************************** -******** Quick Start: Reading MyEventsFile.etl file and printing the events. - - using (var source = new ETWTraceEventSource("MyEtlFile.etl")) // Open the file - { - var parser = new DynamicTraceEventParser(source); // DynamicTraceEventParser knows about EventSourceEvents - parser.All += delegate(TraceEvent data) // Set up a callback for every event that prints the event - { - Console.WriteLine("GOT EVENT: " + data.ToString()); // Print the event. - }; - source.Process(); // Read the file, processing the callbacks. - } // Close the file. - - -************************************************************************************************************* -******** Quick Start: Turning on the 'MyEventSource', get callbacks in real time (no files involved). - - using (var session = new TraceEventSession("MyRealTimeSession")) // Create a session to listen for events - { - session.Source.Dynamic.All += delegate(TraceEvent data) // Set Source (stream of events) from session. - { // Get dynamic parser (knows about EventSources) - // Subscribe to all EventSource events - Console.WriteLine("GOT Event " + data); // Print each message as it comes in - }; - - var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("MyEventSource"); // Get the unique ID for the eventSouce. - session.EnableProvider(eventSourceGuid); // Enable MyEventSource. - session.Source.Process(); // Wait for incoming events (forever). - } diff --git a/test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt b/test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt deleted file mode 100644 index 21fcb5d0..00000000 --- a/test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt +++ /dev/null @@ -1,61 +0,0 @@ -Version 1.0.0.3 - Initial release to NuGet, pre-release. - - TraceEvent has been available from the site http://bcl.codeplex.com/wikipage?title=TraceEvent for some time now - this NuGet Version of the library supersedes that one. WHile the 'core' part of the library is unchanged, - we did change lesser used features, and change the namespace and DLL name, which will cause break. We anticipate - it will take an hour or so to 'port' to this version from the old one. Below are specific details on what - has changed to help in this port. - - * The DLL has been renamed from TraceEvent.dll to Microsoft.Diagnostics.Tracing.TraceEvent.dll - * The name spaces for all classes have been changed. The easiest way to port is to simply place - the following using clauses at the top of any file that uses TraceEvent classes - using Microsoft.Diagnostics.Symbols; - using Microsoft.Diagnostics.Tracing; - using Microsoft.Diagnostics.Tracing.Etlx; - using Microsoft.Diagnostics.Tracing.Parsers.Clr; - using Microsoft.Diagnostics.Tracing.Parsers.Kernel; - using Microsoft.Diagnostics.Tracing.Session; - using Microsoft.Diagnostics.Tracing.Stacks; - * Any method with the name RelMSec in it has been changed to be RelativeMSec. The easiest port is to - simply globally rename RelMSec to RelativeMSec - * Any property in the Trace* classes that has the form Max*Index has been renamed to Count. - * A number of methods have been declared obsolete, these are mostly renames and the warning will tell you - how to update them. - * The following classes have been rename - SymPath -> SymbolPath - SymPathElement -> SymbolPathElement - SymbolReaderFlags -> SymbolReaderOptions - * TraceEventSession is now StopOnDispose (it will stop the session when TraceEventSesssion dies), by default - If you were relying on the kernel session living past the process that started it, you must now set - the StopOnDispose explicitly - * There used to be XmlAttrib extensions methods on StringBuilder for use in manifest generated TraceEventParsers - These have been moved to protected members of TraceEvent. The result is that in stead of writing - sb.XmlAttrib(...) you write XmlAttrib(sb, ...) - * References to Pdb in names have been replaced with 'Symbol' to conform to naming guidelines. - - *********************************************************************************************** -Version 1.0.0.4 - Initial stable release - - Mostly this was insuring that the library was cleaned up in preparation - for release the TraceParserGen tool - - Improved the docs, removed old code, fixed some naming convention stuff - - * Additional changes from the PreRelease copy to the first Stable release - - * The arguments to AddCallbackForProviderEvent were reversed!!!! (now provider than event) - * The arguments to Observe(string, string)!!!! (now provider than event) - * Event names for these APIs must include a / between the Task and Opcode names - - * Many Events in KernelTraceEventParser were harmonized to be consistent with other conventions - * Events of the form PageFault* were typically renamed to Memory* - * The 'End' suffix was renamed to 'Stop' (its official name) - * PerfInfoSampleProf -> PerfInfoSample - * PerfInfoSampleProf -> PerfInfoSample - * ReadyThread -> DispatcherReadyThread - * StackWalkTraceData -> StackWalkStackTraceData - * FileIo -> FileIO - * DiskIo -> DiskIO - - * Many Events in SymbolTraceEventParser were harmonized to be consistent with other conventions - * names with Symbol -> ImageID diff --git a/test/Qwiq.Benchmark/_TraceEventProgrammersGuide.docx b/test/Qwiq.Benchmark/_TraceEventProgrammersGuide.docx deleted file mode 100644 index 360c82005d4df1d1bf8a1885dc9a869fbc0a0c27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196963 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 !^Ne3lGeYJQLpWfaRTg2o3O)f+hWqer=+9v z0?Ug0(OHWu%_})R$6ZOf1X7(L-6eWRPqQ&6UYTh96!K|05>FigM>E{oA>B728El4; z4=Mn99OEU=SlC_!a(F_}z=puHdpL@)V#I_+fBzStu_GP*XbMzExQc)!zpz{*){9Yu z5jlE2JZYS^fL}geA<2}?P^_dtu=bZq@@wL~WI5uwcrO-JAX;n(x8^?B;-#q_hG1qA zOT0DAfN!>pNPIi`xJD=#evGK!f1R!VfF0g=CUfO z1X9<5aa&fh?L-khB{~k@b=NQ1XiqHd)$7!Ig9+67sOhn9RxVLw*ZTPC*Ev} zcDA_OM$K)#xrf*Yu!jH+f-By&0FPPD0E~if=?)RvQ|r?;K88{>Ny}fOnHtTiIps=D zCJ+B7IC)wxj@Y|}Es41V1DAlpebs=aofj?n-Z0Njo@^PGa0(;N$LQC$Al$T&_VO>g{|d~ zg0*XoP|S?H^0S~NKJM}bXzGkd6~w$CsMj1OeV%l@M5(#m!^foCjt)sFGIzw_T0QuJ zT$v7lLw8OhhObD49fV0pPf>Uot`EKFOI~#fP3;uwG@d%jlw94}B&DM7i3k{g&dKh6 zfv`z`j2xG~4M7(~pP+LewrMTMiw$guhmcqyS2MIi$dCb9a99$fx2>kmBio!fYhnm4 z3YrYjTd*rJBJfbV1Pgj^j0-&%A(trjR|G8B#<3DHLtzsl*v?V{Ew&I7zX?5qerra4 z9BtX*C^ij|>+U_P&t{Ho?se3T-QO;_L(idZ*n{Mu%V|$h5O=0H!%uw3_2y<-R z!Vz*{{TRZnEHlC=45$-_J^ zLKw{Uu1^=99u|I8EXwa;+A8=hw!>QuK=i_SH8d8aLa3)U*2x``d>=r*73RP2^fFmtZ949i@#GmKUPa=me0ggY0dc;DDkqHt)FW7Dd zilatk^ zLM<-*{3w{4{)QLAB_JyHK}meKr`dgkDrZ*zQvyh~)b(Mb`Pz$+1Hdy0#1!iTz6n?= z!8Kcm_R4`9Pbqid>0?ldF<+8X!b;w^Ab%L!mt@3X>Oe-~iIYd@1~Xi{972FXJ%~$D zEDE;vs$?~T2A9{UYht+6sJ5|_B$S1v*Matu+Q%HlS&c8XyrXrgk` zk$!V3r>e*qA22MRqj>SLP*uz@QDOw)q#veODwjVLP%+QYO1zk5>m-x^#u$sv zCBq@|0?S|a3G2ti1FT<34EYQEM1&*tuN1ll_9vgzi-UM%SNP?dE=xT7mQR=ZL#Z^U zvX_X8oMjI=bsSUxtch(IUiD$SI5z@OrRN-ED*#lCu|rJ~raCc*08M}m#8W-7$-nF= zXkOlq;&k8M!^r>$TlEQjsKbmL(Plq!5a~KP0I=;kg~$bn^xNe%WY1E<9$Bqx;ppfR%7LtRHw+G)O-Fp6vpDT{ic{9f#Lt(?@&96GZhs|j+O-o1HM@~ZK zqg{NLH|*`LmpX}#E1;lG$1)OFG{a6E-8C$vyH;!_lP_=zP4Yl;T^}Gzz5n^?BVdrh zI^w_&zSJW)Vns&}9wPF6w{QswTF9kKfvtm`ggtNeDR_7q4(9Vu;9xS@cK`>IYa@F& za`4)TqPodxMSKtXDFRD2~f<1t#T)!b< z`11e$?-ktTL|&#ujF?jHz9sJd?W$01FJOG~0L;i72bABB4zw&%(s!`lugki;J-$Di zodUML@j;&UV-=VH`{QJ0<@=3LVknME7D0bYHon{KY5S;JQ!n`VQgwc#<1Vj%KwbTA zO#UfQ<(J?7eho}i6fQ?|kTd#4fy95Np(I$c^K{?&&%~$C zUpIsD%K+ZfR+}YhP9pIxsxW(TaYsM?OV9x>A#;Aao{c1W(qmB z!+M)sTzK}Ay^=pMm;Tdm<9j)p@)}osz^LOTc;n3u_&CA#M%6C}VGC@kfIoo(LgMpR zP`z_I=6^s&e-Dkde+>ist7~)d7#00OT>g#tpI?R19oQWF%b=z+OJ%7a(s-v{`rEVm z7h?3UVZguq_B-)^4hEV%nPem-naky5<=sJ&k(C%;keSC_CTWr}O){oQ#vh2$M>8gE#A)1<~P!RR!p@$SoeI*oOI(O6fYSec{p*Btwm z#=2>&o5s3ntou!8^!4xw7gIic_1Mz2)?dpfG{V1pBi8rQD%W~aohVhQRHeRS=I6Jo zcHL7V%LpX%E`^$Ji@n#x5u8TmIbKY6UA#5sUUj4bsS1*$s=qLIf6_22-3j>Mi z9}oJblK{_;g~`gRrcs)d}IEV7b(O*On9kLwiwD<}6)u@|oFm*&9r zJCdRu=#$~~*HxeJJz(2ABAA&Nca`+GS}4A_uzYlPzDE1QKFDxg}=!?I9$JoE)MpMOuq2agTkMMlxY(GLd^N#2DC3q|P9+xos<6XG@ zsD!b8BS|nVNy49@@RS~w+{Vm=rng02nyZfP&ypfDAj=i6G16xie&DFqO#Cd|a6OQn zfxWBv5>qMpf-CD}cNKWD2Q_~7zvHKps!f;?yJAk+UCAe0{VA_Ij-i_qtQ5A1xU@iR zcu}mFr%E{4eGs{%kP_h`ZGkKQSb<#qBcMnIU4;ryI-Oat%Z%nNL&81@mp)7~Jd(VdUJ<>;Zkus)k3-9LN^WCIHBK0X7Z z1%4n72KL5E3Q2vy6{Mi6!@&wFcK-3Mr3I=i*Ccr;6(#gntSR(xV>hxx^yOZY(Z5Z& z-XSXs!>v2$|AY38vwY}Q`XzT18n=ylttF^sS;{#HG2KUJrs#pQkZ_U>kOf4P$Z?+)lM8NYS8D)cbdQ zS-UN-L}{jGJry^2?=*eSA9ZObui4Jjyr=8^N;p=ym6{i4O4MBUWSKK;-l(y`+Ph_j zVy8x~r@CF*47t0$(sk}ikr^{?rF1xII2e?2)+F!b8^)qddLFA8%4kzqu>-MC?;A9CQx1BgxuYx#oa2|~qG3Nw zR-Jo$-op*jbkA=3Iz8!?Bg&z|n#zz_qdH}l%U*?CQMsYPa#FvBZq2xHHpt6#lg!tI zI_WkmTALa3o;a{-8{ebsn9CiBV)N5XEvHj@E+a3ZY(LvC8}x0Di|nc~Dr6_2%bN2- zD{J}8%v>y$b9BZH7Hjf?Z+dfo3D?L1=m*?xv#b{7)$mY=O-Zk2aN!_=s8Pjyh4CAiZ8E!DwyO|B`>A2n!m}OY?Gp1Q> z3*E}9A_rq-t`#$~mf_d*eV^8PrSc@#U5@Xf-eN4bbf(rIt!d`VPCb}66n-hsN7ISH&mPJ?HNNo{ zBl4Ea)E?A~CuZ9PHtMlC12@R)K0C^=b+VY7l1z>C=vuWq zvBrF-C6F1rJ!JGoGtf8X-r5>6EIS?2?u5>3x0;!y7VfhBVBgi1f~!vKn1%t}5bjr^ zu1lHQo)bT*ja9oS1ckg@md$0yz0Hgq<$-F>t#P9_zuz#;x@hJzv-&8@ z_lrWUr)6=2vrd^EJ~SiJn~S;|DXnnmYt8&!b|p2Y!%e=Qxw$v#hN&=2Q4??KP3<-> zY;J;F*$S9x)ue`6r%-V{Q&pnad`&5n=DVA)7LVut#cFn~$WrVoIvsDA9bbsjp(Osr2iDs>^<^zZ{z3owsr;3x(=Vglb@G<)v%a z&CRg3oRuH^8c%E1qOuHXQpdCJLSM03x#C1_huv92pWNXFg;t+mWy*YLuiGufZjBV7 zu!y8wMJD-tN6*wr-$I`5e${B)I8i8%5#s4Qu(y-$y{q1J^36q?W$2qt&}3sSD}zd4 zton;_)-6#~r9aMPjmVhPebTtg3Dub}dg!ebH#4%dn<}4Iore*fok)CH2}afWZPv)% zb?9!ZU?_tTZcv|1N^HJ+n-wcvW-09vUsFmosU=B0Ndby0niP6vcCui>GmfO1Bx&&c z-XCso`{gWl$;&0FRt%*pTWUV6(*Urt?@i**xF`54G4`OQEk@B(6n^;-e=RKS_c*ahXK_`xDn+++CjeUZ41rMvvoqnn%|e z0+S(rA%59&58Yt&f2u>MODBHJpLw%z`OUb^T=LA}Ku`SsT&E~CeJey=o_bMWnT~DZA!AY|6gn8Y>$!0f zAsNWwi1w<)o(M5<$bUFW8fqw?9j0Rq!p~4R_V>`6edZWuJ8-BLyBjzo(!$jP|9$Zh zg8Gkfi2NC~`%HwXE=6`OWNYZleetRYbJOy*vaH_3o{TLmwEN!N(7hY2Gv#GIyveu8 zK~J-%Mt?{v4^FQyX05)Oi`b!OJ!Fh*u5mwWi{UJ$ZD0YiL2Ca?>u|bou*6a~0*XGfA5|6HUt9u>Ctn(#k#G)LRX! z8*GZ14jYbY4^iJ*vgOL)wiTm+T2;`4{N_HtT8X8sGxNr+Nq1bBK5)zRXxymZ)^feO z!s2d0l{=f_q{J}oPD^wO14qo=@}muFK4hcTcry^HG0Zh?qH*S-7Kxi|Yao@+10D6I z-=%LF@MR2uND8E5uZJ5n`9^Ko@zt`lE(q+sI1~$_*`KKey>f4`ZL6`g1jRwIaiLo3 zC=Vec@Y8aBR4_J+c|Pl9a#ofOH_B{k+{W-?JTyAZT6E`4yDm$sL!Mdn20f)Q3~xrO zTdmA!&X5u4tk7b~p6;M%*PG0pJG&r{N{t6mw^!uMQyb=8lV#ZU?-(}I!W}u+JC$5mvr=x0 zS5yP8s{ps+-c1aq4`M)LUDfKkJWDBZqaGSPxzWoH zxcV^cu5u_Y9WS{~%#_OQdaX4&T8k~;8D*rSbbDUvI^~HdHS-zP^n$+GQ|pbou@CI5_{oaxJ4?M(?T}&kFlhRewWPUGoZKPBUQf`4&SuDrH(6&`oz?ll$nTmfM|JjTZ5J~im|!PY8Oz{biEHb#FDT;I}rCvn)ch|Zdy${rbAjy`&ZdXl~&VEt7*S6CjS(uGOeb49gs-RkVv=h{-`J^t)~5# zd(gC+b{hVDKcAdd(@sweNe}-`A;*u)#k9~(T21>c4mdppCRM;6O99h5oj)9-)524! zHh-lyr-i3}2}Y-dr_yTLsh3U-_?sPQT1`7mGM<#~G|BigF#2fZZ4>g8)yn8UdF^~T5LJpI`v0dZ29N1O=+>^ z)Cr}l8h#iqrNx%t4zto?%Rk?A<`n=aEw=nqS=_YPa$5ZJA{Y?@6`X#~2@eC4*YgP!$d0JJ$?mQ8!!#h~8l1n?josoaZ@D>(f z2Sk964!+u_ z_JD*EBT*`*%DHBm9${Fx@`pQg*0^$3{{ykKh-nmrK$L}zNTd^iIkKiYrj@0eqnI!s z2;J?&!+j6Y4UyGY6Mbu7yFe}Uo5;YuB)kE-Z4v+e=l{N#gy)9y3aY-0LjV5fzjm}H znlQ+COiQS1Hqwze=`I2F_mJu!s#)R4>mQLRmMIfGhe+)Js^|lyFvSuI`_OvP#bmNp z`gDeDqK<_Xs0$>u_T=%H?Wc8(lu4Cgufo;|8BXci!JSyw9%**ch?nfRNINb!9@E^7 zrty=uvv776aCfiR-(C|EbC&$z$QqwvQ4)rM@1MJihVi3e}-lN#YqPm#WSs z)p^~P)#r6*3@!*P;#S!HQ#$nhrSc3d-Xn;Iya)nZ-ZCI;L=Eh&O)st=i~Pva4o8?q z_*yVS40#1q`99|GI(NkjvZ`qe_Z@ZaQYR~3Xd?%Xr4Q(tc=8y|K*i9VuLgD`VmW|# z1#S|qW%Us|5Pyc2Yx#&b_>n!>gkAgAeS_Y)P|hQPM*DBG0(@!z_8!`Tuev{srT{G9 znIOK5c=Fi8?eF*Rp{6h-Nt#5D&MZH);h7-tOdGnqlP3Ja>-Z)pe2ypOBS_CuqDYcC zD64c0-x4MH3cexGq!UvV4-#lN1It9w5ei@sh8;yy9Wia`MNXgaTm)`Wj5kUc(YFwa z!v%C*Oc~_SzEWkrBf7t2?qpFANh!7}XWBq9%acA`d?~UoHkQ5Mh(K5=Q14B90I(Gc z4+Klor|Gf%K8g|}2+CJkz5v^NEP}+1w?yLTgKkar2^Q{Pt1(D;%`id=xdxY!=+_6_ z#|Slr)e?S~KxqAy;S%yIhG#pd;Tf1;z(a=DU!P2Po@wGJGnmq(K`{|*VC3VgaQ%#Jo zPM|kfzHMHQH&6$o7pdCL%iAW(s55;bwa^~5IJ#R zCiq71IW{CsV%ndcAU9*viWv@475h(`A6Gw zEoq)}gnKPq2|rKw>p}y~QG~&F3?&Sl@v(d#poE7c3i+mBN25p5Tx~qQaj^2$2>wC` zBX@hV4)-{hm5utA=Ban9$}7jcL>JojK%(@bo*)p zey+vyHXU{STFv`{!XPttW3du!Ey6Z)S_Gwt4RDBDD~_3r2%(j=7eS5_r&9yVB1Ya4 zUaZ3sU{E^1S^kGh{dnF|qc??Ef#D@n3#l&-7!a9&Yp5rv6F%_l%8-Xupk}k)nBN>M70Mp2)Rb_~8LLejx6}s4$9= z^p7W1z;qntCW#{>5gUwmuU18DXi-ZDg{-j&v&{&3D`Kgy31sdfWNwilz0Q92W3P8* z9#54xftQX}jF4nrBpCtrlkIux{^gzJT<`k0Z~-Q>@6bV>@Y|A4nX5JRe#$n)Uliq11ap=LyR-QCIchMa4UWtuJXFi!>mo zU0w@`t#9cLz_y)u!+Zfeh_O^an4aUy=+UeMCA0|%jXW)ck z9KkvWtto+*wZO3Pl7*>0!8i~N5omYm?FTW*=PRtCkXJaGA|Vg{{DPNr*6>tzJboCz z@LNMg@foOyP2Jwi(?xg$+y!j=68nRhP3VLh1uIjeQ&^4eH^ag>Dw{?@VIWhQrZufEN-U0Zs&|>>pohM`&W%x9!6!dnjwbULIE7A6Dd~i$S1OHGMeu zf|i8jb{6QFI#?YpMuzX^AqpTD4szs66nb^ILePZBb10_%lKM~*qi_!sf@kV;c?GG@ zp$R?kTA*0AG`R4QJj8s5KDzHOmRh(tWUTiNRB#9vCLy2i{^|Kffn_M3Ezl@hJaJtD zsVeZxfR*GuTC9iUHK)Q=3V@Mcw|PnYcP|7In~Hu4>1$;FQTd7oEEPC&bHMRHhNldo z8Icu1)^o`qoRV>xmLAsme0x0kOr{CsbsfT>=P1=cSI{agpeq+dK1wVQ?t@yVA5DDq zRQ`7%{xgQ>;R}(IvqEYa9RvosM;-b^fTxdgwjf>flKU5Y4Vm5&`5-kmwOIQT&ed7z zTT=K4xH8p8w>+uM_efzGk;H*4zckjv%TV||3S_~Ns>kshW=|iC-YdKToTzW+4B#<=&@MAyR0V|p6$(6w$o6^Z? zS|J&lNjo2M*GsE+e9}SRUtZ1gpa9<%Vt&`5{@q+pt#4V&@Dm94qSPH8^_{3ucl1yT zcNY)D&P3{Kgg%BZ#ZY7p5-pIZ$EO9nw-iDuC_E`}ym;v_2wag06nXhR?-47`9LD99 zs}AEp0?%(l=AV(}>PM!aQJVSCE_SWe+5yOZzDoiFfipub0Tv7#2KSB!mxJa^pn1K| z@C#sgB%Y4yrz3~}7+(NzOh1A}&JyQ285iI;3GjktItY1^R&a_ZQPL9J1ByVEGPY<& zkt%SYy8)G)V;KJMi7)77!^P=J+851OXcTe=ttya~1h<`O*Im*5&FOMr{OV`#q+e_nq6w?6d@lni`g zZXG&LKrN52YCi$d#z1fkBmztP&;)P@9336}8i1Y>4>8TS2fyioK1HMiEi+*7(Q$O2 zL{JmJ0YLD`1oFs)9^iOR^tU+zg`0%z zcz$>dfVnt5IqiR>K#Dg!grDUj(|!JV{*#bh!kHpX!g%{jIn|?T45>4E-Yj1!(QxKZ z4oMW8S5cu;k(HzSI!w1eTxmYOV#1I2y0BV71!X1Y7omqr*X^Iz={T(6r%orAYxo}= z=?k?O9+zDJO-A%JWf;(ExkqIf4jJN?%P>GcddN{9$1#s9ja_Bb4@LG$4bX2w7RC># zJi?bkxXS`vKOgb$&E=b;#oL4HcTJ4HPs^r0$&NPl{3v#e=o3sjfOLHNx}5uCQ0A6G zO9W8_uAcjfk~^7^|CL7mIyYIK;T1{xik&bsZe}j~C%Ejd*sJB|BmT==_D{z3nF@yA z%;fgXd$}-0L1qN%EB0i{?A5QeS06(yZ`zZF5o8Kis9P$8WI~z=>30{>=iXSozQFj; zC&!P7A<~sNSrG9yf`}i*{r4?%k2uZALQ#?Kve}z=!C1(}!&pCrl)gNBbIbfA$qJ-E z%Grv8OoHE_1m7|T`8c$AgOij$!ap76b3Q2^EWgk`e763kBaok!4!^0n+8-8neRFxt zk3om8EbsY2gy4h5-nXpOCuvF%i@b7|MK14xv03Eudjuvbs_puzHw)8RFrR*{jrw74 zA`pHb)Bb)^UCRpIbXfJHP$wYRoBR*W6THF}vdlU2KX1_gT-$&Ci3Af8SCnrmik(St zCc!^Zf^U&tMiwPnC=@QGmt=UQNRja4lpia-&k}kaI#!r#TFC{iFIrP_KQuie>@~U` zx0U{og7Eh>e#4gYavZ2;$DvRMID#8~w!>WLLzdm;qQF2quGO`)QZ;JaSYg}(e{9!J zKHvF=qr2S95BJyFVgJu}@G781s0E+zqSaapt-G9C@c?Cbf`VowMkf@_lCW;j`4#>A zinm@$J|qPi9Xf>lWA}InH(nb0p(((pg7dWC(dWDPcqqlG5wrc!umk}e!`e|=T=&$# zfS`|sV0zo376ykxG*B6uzBb@I@EYVj60Qn!cGOEP_3w<=DNCOpz>LPV*svh?$b>mpblRv_)L9cbSS~nZEV}N zZRfYuYeYf!#-uUq1m6LlM=YcUMY>GrT{_T%)aLn5|Xm2#3=9cmfYCr*(x`xdTi zk1rUL?v{LvEs+L=OIb{n&Za7+9Y8=~VvlL|LLSHuHPXt+3N<9vqRpys6E+A;Uej!o z58J9bAu$%QFK<2nU^fj_)i`L;$*} zv8GEt2u5>5ZG|%{POtYItIf*Cd)Q+1w&=Xf{*UidHJ`O3>lg-$v5q z-pE>n0V?OmnEp|FCF4&_B>*3#GSZks$fcA1s;-Vej8$2HGgZQEiUn6xrebGys5v7F zbZ=?WU=6D@pkp;U>+VEt>Ub(IBvdosaK3TsZh#z%<8e=hunmoJFTjn1Eu&%Umc2Mv z4Jy%@7&e9oS9P32fTJ>53`_TNu|6P+EOmi@~Fy#7ckkZSZcms5eIhvl=_=$2wU#?^*k#Q@)et0(WK@=ZX>7Qyo=ltQdJGh2adT2AAu97VWv_%w z1lj~#8|sV`4OOHe6rI}&{l!DZSU!!J&sHnj`wUrj;y7PGe%QEY&^&15cz7C%nXxfH zahb8(W#hdnh}IyMNwt{IBsjQljby>mNfdw@*ItRL{?!C(!>9}730OBPsq%wsefQkW zm3e1AiEm3JkZ7LlMQAtY$UZ8`ymK|$e@{8$Sg>Br+6h6RS3$MF>(BY&^_GD%ubFL_ zs-V(c-G~_Nmd>W353*tKud=9ib7d^J5yP3~u|538U2g5)sq`;tzyG*+}J6^9puEl7b_HB~QVlGk>M%#GSt7~w` zQ{8{$R`k0Osjw|gk{(cfRfGYyW#N3J^!&6q5+0TYve=!e@5k^z;WL>6Op=d!dT_8M&-o7C>lSt>LO zC&pTIiwf&ZEvI9#4H<0iCZQurs0_HbrBAexB}uFk8Lt#vdh{04A@-GV6nqL59I+3J zSju`NmS_xWOiL}Ptlv^;&CZ{|HrpPp9vf74s2`AVla6#=?HqPkmQr0?JP_@iY0hUv zAqvo?tJ=pavNz!0)NL;$Q>!*4STJ{Yu3d?M@2(Yd5>`a+Sn!RrS5Ba`rrY!OT{d%@ zliR4oS=par_#>2PUq^i8^=w#Pc#x}-^VXc!1_d`SSc=#ySf8Au4LiXmU$qjK5Q%A9 zFQ`_px5U|6=;!Mgyhs=y!Y{L$Aa@E{^c=93i=IU7+;y?VggodB7l{Z_Ub=K(hLBFB ziI&VvEEkym`z66zAd_OAkZT99j@$_(sSb>kyt)*#8zJ-w=&GUb$5|Qj=#CuKvt)r) z_86pydRJkRvN~_$PKO-*(Y>|yZ%rn-p}mZtYMquPxdE^Ih8yjaDm<9w#sNB$p3DrN zfCaDF##`0NK>if;?VM!42eecd>1MWGiFO~qwmH6QAvdU`C52D8>auPsx!Nh~t_8Ey zCKK1xSBlI&cmi6qVMedxo z;Ft6pXl0ZZABxFfNh%;nLJ$!2BU4XG@Qx6B@;A`R_0%NPNNZN{Lag7IPWY$~k`GCA&G{&?UzuF_#vE8v??lJ!ZKmQZcDxoCo`nCFhLYpbPzisDK&A z7c#4AQiYCA?Mv{w;Agca8ozP`P%ENkBU`!Ul*yhoT%f{E+kVt?W|sSi&}XmAge_>g z79sX9zh+~@xLw=gk@%R#10mLkXOWep>b5C5&Rr?c79iXFq2l%Bsa$sv<+;@U{o zAkv(Q4VE&axl=`Yv_g2{8rvv7Om3KSAaOEGB8Rz4CI zwELLGSW*n(musG!YkHVoppMR!tnPMXg4k!-^L!w7Akb-sJt=5(*~a=VrwvSDgV1OB zeS}0nnmtsiw(TVK`Pr_3mzCt#oTN@kV1GHN(2@&)S~_fMgf!O-4-VwRMJb>V_j22m z@cR>P(G@sv4-)8x0-77#b%eXcf)$%QQ_!lKJC*Ute z!FTg-J&CI}`ObEs@Tw3Zy|nlDpSf%JH+URKnoS4;&BX{>V%_(PF(&O%^*$%iz*Rw2 zCg4@r-3NhCC$F5F;`SZDc&VrvXgA%+OK7<{68n2l9-YaU8)%jvwM+1`AH+K#IUWLC zg4;~QAIk^wsp4;G=0s0`7ij(NtlOoCVXnO*m3GRi-523v!U^Lchm;E2q!qVfOCJqP zI?VX+$zed7SX9&UOeMvPHp9eG8l{t!rKMIfcih7zRffI8JPP(mdudS1aH!;z2x31O zUGgGM?n`@bB}H|;nz|JOZAR=3*-Zt;+gI#jBvBJzYC8ldD(Af1R1mo}gq0|2n1HY7sww-8)NQ!MV3dWNt~2vVC&q)Ne3GSsV%0)mQ` zYT%0{wEw0X7jjUae@s_|y}8c}jJeT+N_A&owY@3hKXYAV^y0XqP|BqbxLC!KI%B1p zZdNQ%P+T|lC17lp-qxQ4pbY9&ZDlASnL0ZUSk9oqdugy*OiN(pUcFp9v0AB@C>kf) zW5TBx+%u(p=4tI zD!aa#*pG3(cD&F5{*S1IOhKh5Y0WjZu9h));4)D-xPJ9-^A$6K*=V|5JMM=#scPC= zI4-Nyd}=AN);VZ06kw$DiUsSJkocuDNuIrmY-!w_IV8)|Xh>7Gc~cb=RP~dp`o-ZS z%0U~{7g3r>WFm4)YoD8CcgqPjFoQxoNu+YuqL}m~b;p0F#$>xE(dNGS@DlmGC}MBl zZL$n5_9w5})W!QDS&GQz8i^L7c&nCw7`JFGH>x@eqc!!cYhmZ%saRARCEq|- zG&TGUlF3EK*AqYOFj`Fi_%PRCg8*SLX>Kt9GiU9+v-NoZp17nP8kAgOvW72P4u z7e_Drz> zmH?cWDIs^a8Z&3`ET2bS09jJ^uQy=&Gr_Y5GVcqfn3VS5$|*LUkhXdV1Lwahd) zrcr2=eU6OU=~smJ#o(yck(t`*ldt`4v+c5K&R+G5B{rjV>@HdLA`$o;fch`@~Gxpvf|FHFy==fAw=$lqI z(I_~<7uR5YavqpWBmW4W0_pc$C4WD9{GSB)zqUh3@2^aU(y?NuBgLZ*Ws3?||0s^*KiR>mtuhL6P?2wf?RM`%O-rly%Sh zn~%=_{JxbL(x(?G3surbdbLWP$wl@2F#lFnXfG6~v&NQ&U`_aZ8wG}H1iJDGO^(!K zKYb7AW6!+)3E+!VV$e9ibX_b#bdN}lph!yM(7YjRFm5kgUPo6@NQBBQ9#PSz*s08| z-Oc0}X1B>9OL-`N(Ax+HAOk5_n&b@L~$uUNhR@Nb_mcimGf}2d&EV z(}ZLVq4=?+(i?~saU9}bE{6~_zziS_zyRy?vU7-M-|vqfAJP=PP9)OqkN-IRfV$v! zT}^!7KJTQuCnWjlj)2n+9nBAdAOOpQ&Q{be0AF6%6%l|rxd{sbSz_5KzDF!exN+$7 zSf9CX5;mu`j`_yG!Z;sw_yqs_qZDR(FoQ=QuIcml=pFgo`vlMH9qRn>|G*Ly0uQ_{mKz6P598KR;w9<&VKh+A z=hJuLLue=Md`p}!=js#y)u%wUjU7;~tj>R+D<+Cpe z`zUw`#-|Sy3gLqa5k^J~>8#bB@_u#J>Cw&#WbiHetyzlQeFC$&qoKg@o93qqh`kBg z1{BE2k>$~GxT8_=lv?tZ(S)1`DlmD^PH4a(X?we(2w+$bh7v^sZMgFJWyN|F48NWY z^jFyDv9>rA`-sQ}^UV8MgK@}oqog|$$e%fO>)axdN$2;4Pf4Zq-9KN$oqGUHdS~1c z0<{7N0YJth4RiVfm2?E2YaH#Zy z8zFiC>@C3O^M`hv0(84gC0#U3OI8g$w7F4ksv$GCtLI6Sfyfzs+)vP_UiF4%ApcxPB24chNGsMI`}4+;G-_@j;!@D(Yk;kbFk|aQ z@K{m15AGs5ihg{Ed0j+U0ZI$&Ksq;vQ3E!}T(nAHtP?txnjVIPeh=*}pTQrn32%+i zCkl;=f+y|$7vF1d&aX5EhTqTReD82|&M9k95?H*??FO4R^_+YMvetH?7CPCV+SWEM zFwn>Q0cjiiEmia|PU*eBukY*w>|s)zC7OBbkP=K1NnEjqr8m^>G~%thD`4tI!^#Qa ze1~w<>maK4)q|<~Cl+V#=slUWR<}T-?YYZv$tmrOVNEwF4xEM{pHzTL0IhiITlc^<95aDwyxmkGTv0x4kHp8I%f z&eW+U2UjdCAdL4H{2;P`)ZP2*kE^RyEt31%HD$hi%7V5IiabgQ+FI9x&@>|Vv66yJ zNc89==R+m*>SIzidbvT~WEPK!!Yv+>Lvjz~6sV!@_rfD_Gc^MK-a!w+Vu&0mlwBN| z3Gb(d2Jh2!kDva#{?)ZZpr}$H<;_ny&&ZZ-NTEy|8z@N)wf~P_kM-#~mvcR{)QEyQKd9ec5afJXhEdz|C#o59m(+4>u9W zvPBhVBARJ{QTB2Jv;hqh7ea{)7|08vY6C~{$Fi~F-}T>0-?wjzKY;7LELs=p*g<$~ zu$~M{Zlaegc8Ftg<;1|=J$6DcMR8+mChhP3#Z3^d421xyC9P-dO9uw>W9PS$7HE+o z`{1?WluKoh6{NP=(GZh>=sQ;VcBc z2_yFr+BrEffC6|(@(k4t#w6Yf?yp1syFlkxyS;H3-Trr*|8!z4EVIxG3F zjty(g`Q*V{#l}(3U$?rn1^x6xIPP%;A9BEQz>$E+wh+Wv%7?{)Cw*zx~EG|$R9-_Y&h~!c+)Z^)Qd5>bD|7C;xd|)-$oP-41QPp^wgB#ySx3<%k617e!r7! zWb%X5%UIx}htgs4p9&5-s)A3Jtnmmh^L4ttZ{!H?SH%I^G~Hem1?7^OTG15C$Ew-0 zvkg*~fD%r|##meW`gyBqX|FqY5y2LmD57odq}~qTv7!Cl39dXv0#zN< z&$4HV$u)(e7{7pzW2*`ROZ6 zV*6j<^Glm-LVFr;ropvzq$#BS{HgVEk-c(AZj0Q65(ni4N&mqCY~=%LrHwzGn%Z+i z*+Pz$sb{KzAIGt)c7QBfMOwXBR0-@eii52Hg-S+mgkybEo!`$Nr^($<&80?PN!o0= zrC!?);(eJapE9SEGgB;OnqTio#=zs>Y^n0UbxYguPFnd$lyc|)zIOlg+D@=QgwO`4 zQ%$n$K+gL1LL8@19Itj1;v07^i22CdKAK|$tZ>Z@vtOn9tbJOQFHcee7q3r#;eYn> zompMsmlJoK3o}Njs%K23WE=aOMZf3&beQ9Fh!&(1WzwF^4!s6SzjpO6{ea6tG%rd@ zup^!q)&{(KubzE<)W*K~0QXFkxncX-e`6h1{cTTFy_9j@2eTbTGcOQ;j%~3X@6tKB z+6idwG~cDmDSRc$8MaIZ>1BlM5&2acOEG%8t6BV`Py!j?U(K)T0WZEK6canqTbVh` zo4)z?u3*e=;UaoUDW)j4cPsL`DvXF@!P{{a&M_a}wsTU%-(S*Uwb{uQda12`pbhkU zttQv2=K)^KwLKmGy0&@nJNIT!g`r6vS^%U=(=y*V)sY&HfJaV#u5IWns6YcLE9g}1 z@qv2BgYqqI!OZCl;W>8uFdz4QG9Nz!$cu_Yj(XmwCo(ovF)M=&p%Wv8AkT165A6S9 zBO+X$|HVd#^5dRm^nyE|^5J4Ff=j&jL1)qZ?3=QchG&v0LXnlRP`Q)khv_1ka&ilG zLk~K`>`ODc><)XI9O$VIw0N!{(r}(3c`k9n{+wu%>VjwSCJju#^+o&_7Mb~lMQ~<{ z`z5=v2)g1^yr=UkG6fQFnRB+4P_T!5r)ek-UqD8E3(V|#qCWY7`5pF7wEq_uF*R$~ zytJzFG%N{D?ap)R(}~9W|9}xHpdUafz{39kBUzZAUaM+E8YtWW9H>-4ujNi84jrz_ zR#38<5fIJOj=NZMF*Zf&6)a@ampd9O zm~{XFhlo94e3aM?bvuoSwU2=>bhKhe5><0Y?c^6^ z>(NTT>4WdmvX=JbF0d+LwKuT=dRw^WxIQa4M+CZ{%R zq&8U(xTxWcI$V-yx?p#Sz@=DASt(vDyDdhBUV0r)m3kp>68bHh8DT!5_DsMVn~VeA zPhzC71eH!9QApDof*7eb1E;HmUKI*0FHc6xY*%*t?$g-NqD3E2twTqzbJ5$5T-ESW znUAlezhr&l&|3#Mki=#i3u7M~;+lsV1zJSH(l32*t?W~xH`Z$m5~^-H1qDK3HXD@a zWoNvF6U$k5&S*pyXemEU1H5=k&KIxuG!CvYjsHv2<+k=#9YCuW!>m-qZyXT% zYlU>q$wlnvw>ICjjmnNYN2WwthJSldq`kd5{w+J&nRype^0-fjg%Zxzj{g2k5^Go_ zy=g(dvMkbSQf(n+&La%Kovi9Skg4UsMSqjp8DO!gZ&_6CrrbQ%QWCRaNv%iF_8v3; zAxLYp^dxJ|nuPwS{Pg551qIGJ(l;>`b5rclwlQNlS)y4uSy4LKRzGi!gP*pnKTRI? zH+>4EVs|kBwYrAax_afGO3e;@O1ID`t*T1LIm~ml1%sW4YZw4oFvYFJ@EN0u^*BlA zF$-MuyotOPeSuftE;*UL$F<1`MIVgkRr-hubX1Jzo2!YZs!&j%+`=8RJG73woKEG&ms~|0a4A6jKr!*VhEoGODi!Vyj@M8Cq@sM z3v)ez48%Mopx_`cxX$_6R=Ge!-TX>P(3`yonMBUmMgMai?bf+ zQ$k8da!&M3sFq5n&{xu^W3y{sPly+a`r!ouomK@z)L`KWQa{Y#Ey z__vqK>at~HItBdkW@TvJXzNFD58L_NgGn_$!b&5D;gC`TP2KT{Lq4WP^7O(~gCi?a zRA)OGjH%kH_h=c1lNggX>vsHylK`z}fsZ4-v=uv_i zT`3#*SA80~Cd_p6O{KNieF>%aIx{|)E7IUH>hyj?qq_xPi;fPoMW8BO*-mTYu?gue zvwa|uQuaM?wMyxSlsDbBPVA<(=a%)Di2|(6SHp-NI{iQB;aLYm9||^)rlrgAa!H*k zy}RpMAFFMM#em-js8rzjWW{Ebhq1M$eNB&Y-%h8!&Mp!e=?TE|o=86YlN zQ+yLe$r{h8Ga)b^IkMuW1R2z1jplp`Ya$%wSJ7#T6ay!mzyLXo;)tUm7@cDq^midV zCw0RD_#2EWHs(mlW=0t`B@5M~i1Fv@M63y?&IvilI&OD%TbXyt+KfylX&LIBF!rzU zfcg%auf2M@T0Pz(84Vi<=2KmZqW35w9aox0d8JBCg68ILokI(LX>w`X1Os`Z*ToljjM_swwqz0aMO+$qE~NC!wN_L zUMdbRZ&?#gY!p!ydks{Z;q&M^@s#ZFMEbf#+2TRz8V|2wn{6Fsw3k7B%lv4Ifu>uA zbC?Fq(~>n>=Tud5P8*s?=MAF~s(qBvPm=w3rin&Z`~EvS}YzU31cQXWBGH z8Yo0{K_Lv~+y%7=&G!=dEup!wA>$XL|bQOIQ_MZ|vU!511nh9=fnv+G8_)uJ^YaIfS!lFBI zwr?_qj?#ByS9<$E?z`5vRiOx4t49|LhR=hZ<(xFm4Kb%_A+b3ho^4?3n zpLuRO<2(YK=N@tmC2MEn_V5)vEuy+;JP#!5Ga(FYaLaC9f~DH+4ea`JcNn?z;c)U9 z>$5s=81J9O|L}f6XO;JcSvrJoc#qV`%^KOzkj)=av%eKyz8eotCMlj%69u*(usAN_ zxw5=aE#g2?61Gk3C~UioV7Wx$74B?W3(IDRYG&B+u6FgFkrZ!CpUJ|Bt~+sJgKYhF zZ_+&}5v|ip;cj{-V7pKplxHr;oQXWi%Vw9{5Yn>qXQ-BCcAsUhIsMAuVzuzeb*)6B zKZSl@GM9RqA7onn=Zw`JDNj)E@-@T!&m`ZOx5D^S`|V$i+cJ`k#nam#$(W?6IXokj z^9be|Yf>c(&yJO{bXwo^UNZ+kCN#1T&O^y#n|wy{Ee%9g{?4xPnNtTw^VqG>k-Vok z?45^3k)M1Qtuo20i>*fw$<&O=&%Ch)*Xa zD~}7L#_`DQ*zM$}M!1;RoYh{7``1$&Qz$Cf?j-G!_2oO@RTMB%>pnG&Suu?gFG5U- z(sjyWgR=gEuzZhP(;m(aS8!TzqeY%T+_tWp7S-t5Pp84s;nQopszwP0frT3x(MMU_ z7@MoAbj$QB*T&NsO2q6si?x$4##?*8gvOES1al*(gDP*4=nxoB=H$@jf_dy%sYyZI z913*ea=O-{@2vQg=3*PE7wC#EBMrGamflUK+S<2~K*fI89ZKjL^#qfoF00$5j3mU8 z;ExgOkYcTIL4}IK^EBv2)%N8YfUlNiXA(Lo=x~mz832f?+x)G_r6=~?LUTT+Atu?YCTgkR2(2|x$OxkZ7N^a?fSckHf z@)mOz*}8VHHKzZzmaS=rXRv()>E_T4ryY*5BdJZZ1t5+x%xR1rOpBHN=E&E!KC!DWfm<$Hv?#HcI0yp7vHMh5 z@hLJ916|0)UP|XWnZ!{+9mhxX@kSPj%=bENQLb zc=>|K_<~OWWDb1ux` z&OnXF-v4u~Gxma#fmvXd4dN4xA1R&lav}KCAU^0e;p7W8iRFrr%O|A4tt^Z!iTTgs zk2&-nKkZ;DI6VArP}5@CSYN!OOP`6<-Xo=JUIzH*h4oHe5!0OkM0~?!{~aeZ9IU^F z3(@RWc$8dyIHK1M#110V+tpyqulL4ra>#yq=qs7y`x^%6HdMOysek;;m*#7~_bv?j z=KI0O{$m7*kQ;!k#gRKa;2xd)(Eq68B|Sq8_TnZz!>>^1dztZgw=O^q!^DRTVM4Jo zlPknMY6?8e@C_a^&-Yj07%MPj{E@fEF$e zuA;FD&IE}Up&#K^#P_6QjtYZD#9e&2UBOC>vXEEc9+00WB4^oe41}1;8js^KUsv)3 zU0rs4OAaeNw|o9Ygzu303;v~>ogi(DysmOsN6rT5%{(WD8ZE3e&YtSUxn||G`<5vQ z>OmtR?>;gjeZTa6e68iFURNTvt~u&|f-;uEVMT6SL)K~K`3 zfFukqkU&Az`U6*@H9%M~DQQnS*zm;hOwO<}z#c32$Zc$j1B^1jUu!b2iSuJmKubj? zHE49a*~O#wms1>sKGJcZ=4W_*EdLyt`}i6Vu_!IyCCq}rD|4`)eM?Nz(hSys#C$_}79n!be*!MggH4~~P#AgL3ZKu$zSbhHE)myZ z3hSaE6fI?L>tO;f_h{q0DR)*tcT2>F0_eMf^VtHs{x`|@hcO|W+ zB->ubw$3tb%MM2p(ecc@gZ2>jTGXIrZh_2jakf-fg77ADKGj;!szm3O5TZ^{40t#SVXwb7vmhT z#sJiJ&2RyBwF)BX2YNFH^Img@ONlFWLtfWG*m2I76r2=Y0*?IX9)A``FCAE>kw&@& zkm2xHZ3>1wE69{*Ye2j}+|XwqZ}g%n^jR6c23k)-acjGa9HzjPp97rVi>;MUl|~3^ zJ|osrtYx+n|Lr73>}ZRdW_`T@%;vwGP7HI{~!lWlQ z08Rqu-)qx}IEoO6qK{2D4nhlF6}}Ud3AlM(&g0$W5QCy$l`$c{U~UI>e1peLoE5wo ze)wl(HH8`?lr3LBf2Cl~WwqHFUDgzoD4J|DJqa6T{Jx8zL>SV^eJYJI_z;!Mb102n z3~fZ9#RiXTD8mMdv6uK+OkT8S2O?CQrc#m83_-y-tjimFW`52Imk=``|H!4f784?A zVX-aTPFdcxI`(EKygRC?!IuvCrzNnLu`#D3eUBafQmx#CPmBC|FXJdQEj%}zp5;N< zV2|cdlJDyQnp}W!f=hb=23iBz^tX$(w6V3D=E2U*_0z0U>*kNkEB)==N$F{iF9aJ0 zcaoYoz#zA9UahRe8?`l@&QEP4@Hh4n*IQVg+%-!cNAf-jRIT}GsGVqx2of^(`t>Wiz)cB#EP zfBh;c1X5`PrrQ_zQP!zluNan5L%^TOT8u@_7Y#upjj{3V^{==ER#%A1NfTEf2~4@k zlUhGNEp0tW?}TRiuvj2;Q~fpg4TytnL-E4>hcY4Qj*TAqy3_TyT(vS5xz)!3UgxR8tgsEVrynP;gMF~Jz5Uko5 zWv(2fCcvuvF@C7zV@x7K47p&c7=rqIVrd9x=^60?6a*x`Mdqvb1hh!JCLev{#NvER zYzi+Wk7y!%iD>7BAbaG04Z+(<72p;qoKy%JJwnt$zkfIoUnR!m2a)Z=jxOF3dJ8Z; z7dZ=*38l){t5FV^Pa#1Sa7qU?{W-O-f^f~DH$|%I3%dNXe1K0;L?2GYklUUwl%5H@ z22@D-qZd5o+}|Rnhk*e`zwQXUHC=iCG!CuG7-`|jIzRUkPIUYguU2#wE3t8=<@GnQ z0osI)=_6sXrO=eYKNJm4U#A~25-+0Q#xXD4hv*CEvdXU<#ud&Cc;9tF8t76p zir6o*14;N(sVrUP;%2r60HF{yJpJ-#H#?u&mX=ku{77O)e-;L)>xrPxJWy+nJl$1~ zR;-nLgo>8lh?pneYyi>K9CF)vu9GIthB%=mR0ZlXzE4ZiZlORVh+u~+;4A`L+$;p} zFaTw=Pk2tS3H-O5XY=YQW=7sf5h~T-h;|Hurj{z|wv7^LQaN2}u`{uXy=NU56SPW~ zB_SUANX+ltGECI$v+M&IcY$U;DJ|wsZSNZtbCj^Dhumpx4%Ozt4wprBQ`GVP-Y{Fp z$-Pp0IeYw$yzf#A$Tm#iIh}Rby>OPbTjx*Eqn+pJ#!N1*sj$hvX|d$@{OsQCBWUjz6otUg9${H{;RyRAQEOzxS1TQyrUYrj1bL}a5M2etHY*?n(F4O#@p<3|QFOvnj zK(%UFE{FUPIfK4NvC?|>v_K#kEMi%ZP-UmbmQ=F=LQESA0*psLBq8mX`V;1Pa3kc- z_NK_W#`rK^FSI!HD_N27aEpTKw?KbT?IGvR^B~AZsMD##>Yub{nZ67Ms)48-lN^@T zw?EDkWR;kppW0&8mdS?!)=Uim+`-@2_kwVCP&;bcpKqlxIv$aMN3%J@W7dOpg8v@Z z0=3Tbi69wlj^BsI)i+qzBfR-^iQHkx3H541X5S2<|G+UG#$0Im2eXs?^p0K`_Uy2S zV)bX%^ryw4JUh?+<0+r;%G<;!+Vf86P9N*TwTM|}{Q~HwC?qV1|E>-Rz0}*r32yp# zSOGjw5Ah(Dtsa@OuL&fq-2wVbaOg)2J24X!H$I|0Oq^K2j8M77T8+rbZ2KAu3-Le; zk~Rao8&LxihgAaeGDZ>s$Ly_Go%7#b%qd=9LAnVCmq2Q#DS`tbI^PMTxJz3#j7!It zTSE?w+!Zwd7w^;OrgS4(|E{v(myzm`p_&ij_=%yGPqO`InKEKY zGtf=%7Rbvfx4}ai zF@Q)EBTGG&r4V~pJBWUbV9z}P#lvXgLpjqi|76x>wwdg#%O!~QKOo6veG)`%W#Uf2{jv&)3ne6{caHOxts-Pk?MnQExtB~-ix<}&>*`pk#9X4flO25JGa<)AysBOsa@B%f@!ZRS zadhk0I@V1rT-@FpKrCuz`Nbq2?&cN1g)S*PeQ;R0N`+04f#$_XP#H z%1MI;V<+iqx)oa`#DOa2m`q)_e9euc;<(UsRcuo}Y}w`0-B5)j=e``*5MStgaFqc; zDmZvsM-I2wq&d8ar*XfJicbT~PD5u;LK^U#F!9|jJF9699>6OMK8{S-swyvkj9{Mu zQF0Sc!=AV@r(J*RO?b|E8s76A07uA0-e}+u%oI4pi$8b(1(wRLhP%h>Io?4E+|tVc z5e5fTK5Ycbb5(hGt|sWF5JHD;TUxJS@cmc>XL;06rCYgO_7#bw1oJ}4Xj|?TTdzCh z<}oh6Uoa65N8;-r?;;r8QcqXAxlFYte@Aou@r&eaPZQ_;vYh<|Jt)fIFh`5O=Q~XB zss$UjCcU=&?q+?p82@(Adxy{9HNuL_h}kN`7vNcuS{&lOID*Q4%QOW9#;PLGsqOGU z#!=KQe?J&qxS%yMvy4}+MT0&4_^>gA4bq6gx2yiyvgN zu>A&LyiuP);nm$9^FOpkgm^+ZD0vxfa8=TKT-usLvfyg=?t)2*(&D3KF~>@*|M;Uv z<8u+ra6dx4@BM?=mwW}k1A4i)K8zTji|GmBc$H5FuwYlCb(nEq7(_+;>YTt;u$@R`~pE#MA9Lp>hYRW{Z!MP zBI08u>|nWZw>4*l*I!La_0b1zT2VSilMnZ2H1j`hcd&7q@3xw5Cgj~(=WjS{KbGcy zuw=NN6TLFC2e(jua{orShJQ3>a908QUQqDuA6;^;y&R1zJnBqdow9NF=3S0ymG3rY z^g@OI7)W8NX6l&%WNrSrF%e#?e>YiK4J8xMAwGhOjGRB=*jppZdMn;nU7uq{a`>ymf9seKL^W)~& zLtx)X$c0$T=AIqBuK&gkK&xkmI_2O5jqae28u9m^$yZ`Y&@TNil;S$>DdgxVj$_N~ z9gXPd*LMCGqtpkijq`x-s#6zua=lNl;gGQ1@KD~CZLR(N+50O`9GpH{9(Ja-dz2uJdQ8-Lvbbkv#uv1pPw4FE!Nd}h$F8u+3%m=JuJw&pwL z^_Oa8w7w@HmM6t=E|F&$gsYY7OMF21EsSN#n3AmN-s1=iw%!1oov>r%S?YLy;8J;w zMSi%HY46kW%Ar3Z=`rg4ZOzSUgk%i8y&M^xQdU&Dq}I*nLnG>4nQ?iXUR}L(i(%bi z`1^c-yOk!*9|M3YXUZYMS7A4??q|U+b8EqQ$?!lv&(?SY34KEWE0twM2Iv!NTQCSH zf#j{62G*Z^#L>PzWEdmv#FDS?5}$NeIEb%Ljypo%<*7m$Orl~&Vu-=k8*eD4e@dk> zM;@%aqt&5{qqJK#3e%c~+pB<^sQ^|yTyJdaY(zX%SJn$JpUp7wehtMD3 z(7!9AFu16Oliu978Vm_(lkgE8(sv(Z1Bd(|A9T7Os;c}N zk`6Qn#A*P^Lfn2qAaavMMT-jp+L-vWrXs!-1GjpWm!!ZkQAYBOzWR;0d{P+bs7`$j z=~dh+wm6N@#Vft68gidajn$yLWKs6HdwA%-P-RBi@daL(I`$e?lCTVigNm%C5qr~z5g=!n-8|F& z=ChGF89`cqfd9ZA z@!sxN9h_Q>aYOOHttl+fo$A;^{D|R75vb3xj4GNXpv$Z3?Afi;z}f{04o=dDg?m9@ zX%>;@%)QlT$De|8F5;5y9h{=C?S_sQ$wc}H;6>y4Z^mRcTVeMOxgarzohtBzsNL76 zzCwA4-mg`!iwufv`vVlYghN_au(gmE7Cf-LK|#$VZhE1}OV@k>`3}GiEb1&Js%+{5 zcW#nR2hAD+>8EIXVNB(k`gFa6`Y9R`ivRTp1=3Z zAKhCXm?#{<^Iu89p5>4>F)amGb3C35-d+`-t;G%n2(ejEgVu#5u_HR!cq2&i1c_ZI z+V_2Y&3-8A>6L*XpFux1izy@?I7Q4UUwncYoFjgNDdH2u1K&OVG$awZ$S1i{z$45D zVc{^=8@Jd847kB(dy8>jtLHq;-s$Cd-I0MFzPZ-lrfjd46|9qvv|+vBgZ?h&Q8L8$ zpNSDSq$xx9jDz9A8M*0O<@b6hCXHQvRMleFjeyKp!w0kK$3Ga}o^#0hL2zgK9{J1C zJzLFlz%I`l{e*M-bx9{1|KL?c8Sv5^%c}h7xSxkQmtn^43+%bi@Yci;qXx0dWJIHM zL23IQYkk3Dv_5$Hn5F5^E2XBdIm;Z`U{HOUGPqxZKFnYMl#kXG^gKU>)b$+?Q|C>2 z2=0Y3KBh_fS_r&ysvFRwN<5qxlB=GweVTX#6)5{j}>^$fKMLgXxce(HtOu!#da zu7?;4W~!a{<{Ll*e|bH4S5DRflwf`CzyM+h12X&c@I(l{us(Hk&2?tb#~M~$UI2$8 zYb?5B4j=O)(4&w|B!(BkA}7$1n6LA0`f=?6kBVkpb~NOP1xvzmoHQ{-h~ zN22k*7?oM~ay-UNB$0R4i@^GS;VQEqK(_@}|qN#`{i-%cZTmbe{p2BCA`iK@WB&xB4Z zv>hV zr7bzbbBr8eMiN9i)>7w&8G6ID7D;{@dE|N5nnRv5PMrre!-$-Wt)aElUP%%dZk1Zk z^31zs$6!J|o7xNeU9>p0_v0;AaAZCkpB4q~m>Q0;PKtHXTj`{9v6v`A-LqIFeH@jvnT;vFEE|)B49qC5%dsNMZwKxG zvhV;Yq{4}0lR>PKVwJSDCtpfs(lQ^rNwG@$ohqr`_uz2w+RTEwVzFNOIC^QbR1EQC zUZ9tt3N+D6jL0zzv$fv1t(Tx~Ostn;z4SycaT$SP=oD;kj`h-pUW#);|D@tN?YQ-L zR^DY+_7pgc&5Dm>R&4G75nr|ggq0aK%C%(}PJoo9LuLglJ(v3yn-%{|vx26S3@x!+ zWn$QjN{ce}SpMZ7Xja7f=^d?xe7+X13Vt}9^!)A+aeH?N7I==K&K=SOB26fcqt~w< zJfzQ9fr<1P@|R_1tD>6tpxWeh>!)vr*Ma{34T1XIGGp1ygZuXX_47l zapGhSBM(E1mIa0&H-^@^KnsqScm`V1_U@Bysl~lgY9Z)E*rIs`b}?;=El$9@x(`GQ@9#m2NHbzo-iDI-u*}ROSVW{BSNnRA?vt>Aw;wDoKHWxO z!?@<^n-C!pox^6H$IB5jcQ$x|@Xs@NIV$AH$SliH+3aS>@tBp&S5&5c8i#xgHHJ~d zFh2L9`p{7)@3uTQ4{m;f0!vQ`NLeQG7f-zi)c5s(eSC7wfR&bl56t zeYy@q#x#r9Fu+|xAB2PMQmn)u`2v-?)rC_4S1 zQ>fim8)CYYN@f53uetKKOg-qcZoXM)2x3*~*+To8s!^(Yl}iok^XuY$ZqCkPXpOb(}h_oouA#53fEVo zD|_S#@^#m^dRWYBL(i&p+slha&1qKJqB7~0^O|ujlrn|VTvkTuq?^VK-q>}|_4+Nw z&SorosNXQRMmcy;`FT1kkJL$h(N3lKjwN)eoWF1jY)@`hsl`yUipw57QCnS-pI zt#zh%lXHpeGpu^YgU(@ zOIu`?n+nZkhZj{zqc!OwHJv1lq*3ZHg*HF2DxH2VIS3q1ALklL(_=^abf)g3Gj0%Z zMpI|6WOjzAT^MP=AMoY^Ol4G@xIv{@RjOrmmS&yS zh!#g3J=x*4ti-49>UY9ji8m$OpmaBLuI2LB8JTP;+3mJuF0bxZQd0c_x1bb zl~~CoyN*$3WpP6Daj*KBsy zFj~b?>2A`kPwr`Hpn0{6qS|dp729sft@+&@<21{a2l|d}RlBurr?prtf(pao2Ak>I zdtqK(W?9G= zdqa7n(U|J+1>7LwkU`U0F3N>UrFX6OSzoNT)UI8Xi}^nF|JnPlEmyH*+h3_s_fe-W z-c^r&0E7Tx1QJ#~&I1S#0wlZp;q^c|1kw;|__{6Ia0$HZmRBfeT0@a7z@jix@rV2c#29nvsC2*vY7U;$4#0)T1 zxr1z6v&2h)+bFRmN#K=#DPUr~${i7P4Y1qzj4cRupqeyw!CHVQ6#RlAx5v||53HxK zM^8UIdr@-y5i6^_EjTxlEYt19U|S_I}O(d$9Y5>WFi9cif^TdjnAPmPmSz$ zqv=DE?8SV=lv@;EIg2_S6*AsQ{c*D|ws(cPb*NSan~RP{d0Mjww?UIs7AFHoCMP3J zv$GM*8_`h2onm%X^JpDpox>}5INI)4ZXbF{A;Nlyp1SOiMh7;QEO8nj~HRgPP?0|)xX8pniS?h)k* z%=K0ngP7eVq#^ty)=4EFtuuVxTA3>8gCR+T2&+eiU$3&EDFMb5_;64|t;h{E!NiF+ zJRCwYVcRIsKvLM}NC>;s-s^baTlf8$(oY6>Lk_M`63ApoibC$Tw-uzgBZ<=1L$3xs zyl}F9-0TK!yNRTGjp=&)kVgd9#M(&Y&G6VVt*< z-0Hb|I6B9g>0QQ~Xz$iPLzv9s+=DqVgUq(dJq{7lcj6FeT<2ibl*QY5dM)5%zhM<{ z++>L;jwkW7k|nPXV2a;H+l~mr++Ysa@z_^93dJXWdnrUskECmPKWbhIx=O@BfNks9 zPP9*^UUnW>0;wfl7|D&jMWD~sQZ=DST&7-r@e_L|y6Q8lEfnT3$W}L70Sa9FY}VfH zs)42y#Yc7MO5Q7YLSte|Q3Mp3gt$+Cb+97t6z+*QpVA55Q>>$K^Kz9AryT{>L$Tvn zrcC{UtIpLku(xc{Qe$~dcc|``wC`h#te}c}FU_aAk|ota5>no7_xB7SSfe4&l68@T z^c)0A?!`ckrF_W;3@mpUl`z`X&d0s6ORnLQS1kjoGAfe00M*R)h1-zit;aGO#P6>D z)EY}UKMG1IZoQ%xw;U&F@dUSUiXR%(p)gzl5Dx-^M0sSH~hc}zGa1eZmTjCWVQm-lW1xOk=-_`@@m zO=>at3iKVL3DSkX%g_5LLd|Q;1s8g;%H1K)V zLArm0oIKd*B_8uZkZaL!e4297uXCse7q-_-qGtUxt7z>Vc&W6@Zhm(IlmLa97 z51D*$iV;}5xb=o}t{2PrCBt+CO#z~~xmJgN;nUwpEUXcwAse=4d2P>H+xmaLW{RvCHGn2vU^3k#xy zdvSKO9WjoWkb6`jW3H|MQPS-;Eo@Sdtv$wxnz>`_BeYSq&2))uOc+A(vBD@{<7{>0 zN!fm8nyPpA=hcKJ^NIDFKvCcgzfUm2X`1Hly|Ot6jY7cpFti`8<>dh8Lpz+e%Oi(r zm9PjT+oG-QDdg<-y+C!_z2`*0aS9?e0HAwDKlKW?qiLR|=ctj6X;}^1z2?scjLuE3 zbHK@@mxt0V0?#~!_){bq+~(kBP!bY4Oo5~xA1JXC570?{<{YI1dhwLR&T?5xN}U)Y zZYS*)9FH9gY+;@yz`EJ55O7SiC@Z=>wUHEe)3XzAfAIP><={c6TuJLUSiC(+&;7*v zbSb&=Pqi+a);Hq|>>RGaIMHvhuyk40*+t+m_NJNo33#YcwpULR3*X{f^KtO~Q__{E z=L75nLaikqX{G6+X(3zbfNDL6C%bWZya6D%GmtjP&((^0FjZsOSwaFzl}Yl-v(aWt zDqWOHf|PI&YU=PU!_&a{P5>FombCU$0cp|Nsf;E`DN+A|9g3}nO8%+r0HJMYrlAY zoo8(m{&`K@EvdBEwB3?X`*U}zZ~iU2HP2s`-7H)ZbaSruPm8o)UJ|zRNakBUKjps! zVG~}Od*0UJCA(eHIll_bpBG*ePoH1bbr9?L9{FQk*6?y7^Y`XEV#^5~r=4Zt!%u5+ z@2gZK^(*I;g;DbhGzHy%Zt~(69LHbI5~C0l{BV{K0g*p^mVfU4G-rDMwB}8Jx!-~y z3%`?f-1Zb&5`aD(9K;{WI*w+=Da>|tdITzW+`%n?|;Hx!c_jt}uJf z+<*eYU`w{>H4o1lA0SVM!lIgDw^esdgBC4|R@~tPuJnNNAvPgwgERw{Bl4|CgsCeA ziud%MXoGip$B9)PNJD&LyWmvESgzkNO59(%hf5k081v56H~G!5l0YOgtST_Vy(1MT zuI5$2k_87poQ$C*fr94lS<2C*FtN*w<`lQOc;+c|>QUU-XcOz_HVAuEQKP*%ECO|n z@ao-o*bM_E16h$LX6hvT{fza`I5{2nI_XM$Cv}iwk76n#h-I5p#zz@7;eI@#mow0s z$rusZO0j&QO*UtJB2IuA(aN_0V=Dv>ES%cY@k}hA&Xji>z#`wC zE|Pjy7lGrLT}xXD18z_Bt^gKy7@4CZT@J2gsf?*ewe+SS4ayRPa>k;csH~7ZNR1{2vH&&d^u%(H90EB&DwD=^IR?CURL5N$9RQG9 zdUlMx{uwSPPjXGCKhk-EX>5L3_W0xT@}x$O-%ZvqSpqvd!RNE~0CBXq{PYCNu^zP= zGM~^^)y*`S@DS6Umtpmy4P;7NM=%r%?5vn}fb3%NcYKg#GzAlP%wRBYR*+>fDwGG`~a+_IYJcA%Y8eCPZAz#cc< zb<(0-$YBnSBeKKwNTiORTGU4-un2seSW*k6769kjy!|L0_>u{%7v*a@Fd!BcgR=>X zGwi=b2hfEMe8~j9C2*8`JJfZ0M(FvidG8B>=|Tr)(7wQNeBrH0S6}i`ArI9P3^4Q! zq(Ad6T44K}n-YGzv}lGOzgdOTdTTuraJk{eJ%^2@+o31)AaLLBWO z39ndoTit^SZJEHgHJzazshLD5D=@~>fP7X05cE+-FNTm1g8lSY7zL?s!0!^yFYo`9uP(5CtLfl$*eunY>s-wPUdwR5u2%cdq;EO3|E#Wq zuNUjkHglcR^7H<~zrW1&aZ%r$V>bIgE)zQh(AySdyhZf;}7~h{&r$udcfOq%q zzO(NX4x*`iP%+ms6nD=axWHJRVNTGXO=lgp%rX%OZ15|sqIvS1%vLom8pZ<0F?_~}@ zPVV2D{#$MN?Yn`$EB&|H@|$-9e_#4<9iiR7h6-N?Hi{5Y8M5g6kEH)rfuHK!+v&gk zADI68!{QGZgK(Myzv$Hd4~swkY3d%Z&rgD~~64uF91cb5FllIeGr{LYeTfjmW_ zV*U=6+&dFXmN=xXc&j|6elALxO>0HK%MBQIteA1C| zGZYMg3R~HOsVH8n5^CFBI3SNF(22r8Lrr|%UoD3$m`9FZ);TPiBHW14;q1k`SY|ZS zj^7V-blkpLD5}3lJH5P$p#8^9u zYF(i6{lpF`eKY3Gcq7ZoB$3#Sa#~bH10W?e@MvdLi5R#U6n%btw%Bc9hl{|^?+|a1 zTh&qX0XiqmnK@nk#VNapMG!j}}?j?L(H@Cljpx=wY z@6^pf^WN>c&gH)nOGb9!4&9;5Z)eH>HqSHqo$J}(ACvgcq_I+1J;Lmt#bn>C^qiNX ze}~Cp5Q41*C-d{wU#TpFqR=N)7J8?$?^Kq4r?T%zwyRJf9`Fc6u&OVl4brLr1ULF(M+@%P|UaK285m~8^ z_&$nA?vsSC5pS#;x%F4)V5qeZP%OmmZIRR5vsA`gd@B}_bvJZl0}GA-j*WZX?s)uk zFNLPRB2_eqX_@|>Xh1r1K6TUBG`&Saad%9uz@xLSCi;^@>Aah5a|5z&f{>1Np zLn?a=uhV&iRT%uU=ucxzH zLLt$=mCmLX`>#%CGu`vI*A`NfV^R7~v+G~#7oxfZ5SIV}Fn|iJz!F2fJ*f+FHoa}s zSzGRO)vA#l5wG2O`a7Ndt#p>&jM`!Q-$7@I9YYY%@2Q`Dr?c;L_U}h$x6g-nzg_-e zTy|NKKg;;Ezd(aby7Nf~PIyu{@PC%qmFrFuJ%1(tC4bD8-2D90yz+lC#Q(`Y`EK~B zt$%0_;LVSJ%$J@4%g6I>G|PIkYe4=>mS57Jy1DuGUw)pul1`4{b<8h)2VOf1%x`_( zFX5ZdoWzMYNjvscQ-%37zr*7}0$)22U^qm*(D;elf2gk@Ark*{l--J7o?XI4zSFc={R8^j>(V7J%RL!$5uiy*on z8_td}VFcv3cHqH>EFJ>1IyxTxwCJ;VW3;xQ?GpKSr|mx8ov^?za`y;R>Nyjk9O#Z_-21188>(9aJ?+A^=bsqZ z-S*O@ks=%?lCGshza{PB6eX_VK5vevrgR^Ay1k1w5C3#EvJ+Qziv3L7qfA)1fovbA zY()F3v95ZG0M&0Qgo(Jqi7~3|<~zOLqV;=sr$EUJlyvq6tzYMGKA32|X4hLqfASP4 z16N82$)~lIPWHwZ?OJs0c4*F2Dy!*UP*vaoDY<7k^}{xuPA6rmYGgQFZ^N@_aWH5U zHuRd89QNV~+dg^}M0n+s!O}owQ1Dig+3wMhFar%#X@VB3eO(PUS;rmAT;$kYW~=xPyZ1(+&WbXag!0&rLP}b|me{!^|)jfnTnZ{GMO@di%IU zauN9DI?1>5z;D^`=bNIx_MG@)o#c-#k(^&X-{hKsp=c-hTUAdD$aS`aW7apYABsBcsUPRuY zRv7dog+9~-3TZb4-)Hu{AaN^Uy#0+96+DouOeZxP6U>3xh-_Uudxh=Oq@Yd(#_@@za9zjOo3FPyUyZ`WdPtW&qZV~&xUS5u zt>eetcGD7+%UmL(g$W1td}TcC2*AOx7K4Co+-92^2)wI%Gnq?>Rt-@MmV!13x{px0 zjjqD$RbMa4o1J*j49CqSGlu_$I?L7cUWzThRcD!9&Cyo%`M~)`o#pUaXSoP`Ew=p8 zI?LTx^62)p&Tw(`C`+BenflkEla+4$4e2*1`9VWUrbd-}_+2>)tuPrV8E#P5Q8 z9DiwaL?FlqxTnC6_vvsLe8>5BoPWppcbxww&X2mnKgl1#`Pa~XyIVqg*pxaqZg*@A zwQ0U~j?aV~YA#NaGVs`kL;d7XD0(ywx9vAbK}J6DXd_QOK#GaiqA*Cbx^w8We}Szl z1t?HNUo0lbZX9-9EyZCIHtZa-QH)ERs2y${#Uc>JVd&Mvo`a#Ww3B4yP5ZV_k&E~+ zgZ<>sKq6uTP5P#HAeAbs%+tQ;;8m=Bg#o2HgOgk<79~d`_GU zz?0t0a|;bory<-PQLx9BNFO-uWFYg7^FNOBZ>rZP+kYs|6EAlpzQFmfyCeSq&d=?J zUvM6WUK@&FDEuL;CvgIP$N6`hf5-WEoc|`y!y%2`mXzxE<9WmEwmQR7EC6cHe)2L6 z3|dkv5;_X70VVdqlYpnme_d$0}eqY-V$3nOkcYO>&@;>P_@JnvM(L_3cbJ>2k# zY^XH~Z5@zKd*|$8p)2hA^#KMw;*PCVBzSN*Bb&@T*$Nm7hy6C+iw;!!I7lDK=Lii- zK^0H_CNaH-_ZQMblA4gjoVX)gR++KO0c+jRH7pY9cz`m%lRd{oBC8J_KJDL-JttKrE_2-0u(5Le56?I-lzt{sF1`*R1_&dVfIb{xxgA!tx)G zx_`~uuf*X;Q}^oQdYGTJM}NCQXA$@tQ}>5|V(R|$Vp;-v6VAW;6aW;2DR3>Og^3Tn zh)EJhK0XHkdGB)YUQ0V)llNNM|N2^54aRS{@*8>)>vp?cYH3k{j5p>k%-ibV8%&!P zg)Cgfj6Jhe53EnswLGV_5y3fX6xBf6e66Lu)wa@K{p5rpmBjAFg5kD-O;LSWN>sOb zDk6q)(6%`5r;zw?dpwD!v9%U~RE)h=SaAhv5P0 zCf-1d7SdF&knvVXsQq<>`MAe+;Q=l;GNG;y!F2_}rtE-7TDv$@cz1?i+k?!C@)_SL zFecnpWCYwg$4+-f{~)7l3g`zH_-sz!dl7##8h_QVZ$;zu-@6yFACVCVF1?6}zk8Ph zl%i02sha&yt)=~)T@Kz$W&e()vON3KQX%`(G>w_>UDD5rWdDX0UBpt_`XvdS_2hdb z6e8CK1vo^$F6f#`=!b_Z7WOO zuOeiC!M)Hu;lAbI9Du7fQW8Z`l=RPlt4N8PxQLQ2E-;VreJ?N%Hcv8KWXE>wL~(52 z#D@anP$X6C?y9c(Rk6FOUjD%oq}X)_$p(~&Y`&VE`2m`Vd)G^aqox8HIxPCWztypwx}C@y4WXn( z?CxuV-L5>Jns}Gr^jUGI3YlYBqrIBwXE7RN>5S=8dmELyClOcj)CxDlOdEzha!0QW zZj(Y*qGWsHWD%gw)C7-4nmhZ96Z^mkTk<46hVnC=`_sL{VQ$XHWDh6+AX~BrRLMDL z=oEDBpxm7x;Vq)OjNxT!{a~7{3f0vFUnWB^c4Z%@1o~*QSNxkL5l|4k zgh8Pai{7NjD#!Uw927&^6j?0o5QiSUWAOn?3satXG!A9I{})8r&K`6l8`V>q21i}mG2*#aPCvMY+9LZ^dlCfh#6gNK-m2U5QGGjA|PG8|(1@XS!g zh&Ljcv1nVfY$*3e1u72bIw+oeV9D(pf<rcid==CZq7g3~nul3Wl3P;YJ$)M_)G zF1u6UyqqT*n-t3?4$7|US8#0{^xJXJ8~OTc#6cm9BXE>$VQ33O-!f%@D0ak1w_YBF zKANZ%|K>tEq)yLTqfitfkk2R-yA6aQFiN&4v_+wGi$YrzN{h!GWBw=#_1G4LwkY&b z6nZ0He+d+dkqpmK)e86)g|;a4%~0svP3#hbDutJs?#n|_>`^HC&6)uS1tHDeO_)Go z_z5W5CRuHvXvLEjivH!Hs4QqZocuv38nsZgg`$r`(Hr^tOF&VS$3Ylks+y@S6uk#U z+a#<1POMtLv{#}$G3|Q*^&5#*Tduq&S31YQucvB-<`X-$lY{-?&zJWk9xc~U)g=3t zCjlc(npYS`@tZua4?GE^4QjWZM8%WVllaSf66wIiL+LrIdr=V>%+E}K8@a4&x*sf{ zNVXVgATDRXq}l#2S~=;n>(Z0xjtBjXxz1O{IzGU$D&!wI{Yopz_C2ObyOMz9$Sh@4VEvJ3-D%04>usO@M0&2}n}WGCoIPO)@N z(S3NmP%L0GFvrtDa&mPgk{}mVM;fq?_tOo4paokK^v<@mBr|Tw+++ASP?c_(2oP+n|x?=^{x9<4}#Kc^xAg5YoiRS^=rL8>^86SqJWX|ou$ zS&Z5&Mr{_OC$ktWHh~K~Gh5+uCnMe-gsl=P)&zMGgfn}1Jn{IwY=xug9JTvY_b}0L z3I%cX=p+Tb3eciZ&6%H`*BS#@R*=#3!)%3Nm8}p>t89gv3;>frR`%*_yWQy~JIs<$ zP5aA0LMd7qcFE9UQ2(S8N|uhr-oYM+fwA_0^~yXAE_JacA*U{PdnZIZFhp^2i1!m> zIv0}elsP#|SXHFn{0tFrcjNXGj03umMH#e16NuQ|_3^PfzTtYqlr~%8*TUeBir1ab z1@OPSY=w^&A*d>d{(zCkKGz_?F&w@z@(_iht(kAld~4=gGykNSA7U$LTg6=3WQ9*B zD;yo$WQEU2R><1gc!ktN-b=E=YfSv3g$Js7gWqrBQ3}H^yLqwOgv4lN*Kce*L|vcM zu(k26jc;xIU(d!XRq zzWf5{0bL3tw26-c`T!_;MECA^He=ZO@=vyQQb3A+u8I7Ge0lQGY6Mkj!SA>6I7T$S zJPZ-|O=d0xgFgH66xJ3OYHfUL<69g5w2j{&VLJYS*Yf-#1#gQ83jHk0CZ#VwAl9q? z3SN&){eZxTI@umcham0AAwz`>isvZ2k&zu(`|^vlAT*``Fe=?NujP9;ujLUoWUa## zO(lbM-RiZxrfSt|`Hfn=mbap%miGIB1XA>DSb_;V>MCo2O5m=PZJkUz6Jk1BX6c0C z&6=Qfm&~ZGE8fPTf!L(=fLi%>6Fxk1Xg z0Fv7>U&rjnpJVnDp0L+$_6<9~YwW!G(9UB}2uE8G4(ZLLCOARj^J0jI9!Qy!e7-fe zn0-!(L$|*qt#IVbO>GsEt)0LBdA^;0RJ`tder-EHw~tweiH;re(KOmsjllwp%~%YR8)3&so?-B4-Y{`i`xi zT4ll&o+y?;0gQMgk9*2V+a>OREf%6)N`{zGP9ru_;Rgt5q|km&Q5MXi{oX3jx+_Rq zfr;TN#IdUFqMuoMcGQb#H0+M#xi?M39yF)p?nx4Y>C#`-1XsG46;qJ(r z&4PFKV?Nr*D@auP3e~V2oYb}+*=6|U=#dyj^Uo-__-;q9-l_Q@nakbnv0wXb`u!U_ zpMg5zA3~i3gxr+1z6Ux<>e(qrTF}{o&K7jO8_-zSEL7dCZa`ImSaH>YuWR5?5QF1 z+hAYhIoP-K5HHK3;qnlF9Q{7KoOX+TTlCwa-*f$h7jNSFB zdTTmh28Y!K50;3JH$dxrQdZ773l_&IffM;{$n~g2U@~{-b;#An176;GV7~8-iY1W% z1;*q&nWly&c>%f`Oc&%qXx@`=Rsl?RfoM5(M3y+4b|FmiAi4{z{RvD0 ziS&3$!JxUeWK+!Pc6W+9oep~LL5wG?(eD)q3ia63ST$>R!>+gPE=D}xUmZt#k2GK@ zUM}#NJh#u{vcF-oD%Z3C$ChkSUf&$|2eX+5C@TqeBXgvIQF79NRl#P&ye0@Jww-M9 zGtdi_0<@2M7YI*yrij?s%?iWS9S%UZ0HP|{%5|2m0&E!bAc=RDinht7FZ=W!Nj8lg znQ-yu$);Zf_Cg3lku>|_HE4bsWQG31uonYCj$o<{l`V6A5!hQm;HL&dR?BiL+J{e@ zB+45Cv1eAl49ri$C24l^jrPkvhI(dyrv4SNfKd_^SO~f=7NxM1yk;vO>9bg{6v{#M zkUta)o&XvAc9Bah7OVh)0$PhjGZ>H8oY7o6u{&{8HPkD+aq0wmcz*X`#!E|yv8}2X<=vh7O;S=G>RD$OMn&3j zqoTTK*=A(f)jh$-PNy?>t6!(340OD-x&0L7osFG!oV;S?Bd?^2&r6RgDPaf{#_Kfl zPxisC=TOyyl6Z34_)gW7sulf8fXe4znpQRyfWYlF-SIwug$=}VUryBg{sGMiE z|4`JvlYiei9Hy_QX)SNz3-*McYdJ?*P3wDKXx#s-#J!$7^;kQ0RVp~jvu1`j&+CMGVAq$;CEN&_+fhr}J5+>YtY?<~4?` zehQ~&`m2g;HJ*j*m7`b=h;CVR7^$9!zF!Z}#t!YI9Dwq%v7Ka|T@OJyO&c!!ZCnoa zKRTB-|D!Ya0z1w0ATGz`fBs%lNu!Rdr+0r|LZXs#l-`VUN&KRse`?YDnj-J~&HlLg z`10+q2KQB)|Bn}JGuzBIv7BtbOq(y`Jm6keNUFK;@!lhVM`0@g@f0J}C5i69R#<>( zlDHbS-oNrFY`sAFFq&5VyaVqa1={6F){QF7IkWeS^(( zaztvidn~~`jzf4=8_7KV@c}_V7)Q{WJLO#^%r-N=x!12BR*lSuPUCYK)!*h$D>A1^ z9e^+0oZ4_yzEOB6HP*!!ia(j-LodX|=0i(o2Ws^BFO!P-n^cF#S~eR&JKX zp8%k)5TvIcOT8DC*PO~>%*wCH2Dl7b-~%uS;y|E#VfpZgb@gy#)0I{%n+r8=*Vmu_ zqffm{MQ^9 zn|dhAGj8PD^>O;CNUIr89+VxtVFGo-gxmKa<*Cd437f=+et z$A_fkN;mf-n*{pGf>RP96iS;Nf-cllwb_~QePU|>!7-nEMK z?e7i0geYH904fr7uWEt6H0$%2)JIj+qIuZNcFIw%>ta39M%}F92siDl@2>8uVrt*> z!^{1jTlGCZzWqi05g@{gl4J`j#8Njj_+JzESK#spRbG`Ua_`$sW+<#=AZ<$_b+}9KHZHgT>?nx z?U#oEq=(TVJvF^@_Ln16idZ>^%Tq2LrVmb;>7DO2ookB0HIw*$TSSlP-;4DMU5)2sGN5MH)l6&%PbrTH!Jao?r;rFi!=Ci-p=_p9~$PwCch za-O~kqqOus(~p&3cLmR{C>#D$cmDS);4Ti)Bt{_5*oD~Y+*aqdI=9ui-=}jwKL4%B zyVJc84C4&NJqR_ly0_K6t?vD?KDN5|Yr6Mi^LnvW!KJc=84TgsRt2{zxK+V#p@NlD zn%TiMQxoP90wdVgGyQp|<`b}%g$0DdUVJ@Z%eYpjenqF&YeQOC`?C%*f2CUcoan`6Q^>~$!|?bFg;|aysh7|9(9Y0yhCVez-#u{q>xM@E_Ui*T{ox+9 z_rjZt)%fGr2iKupyi*>1OKH5_+uhfSll}N+%3qDuf4-ekq!9>%u$S+_ho46-U#pAK zsg}rU`^rZ<`)L*yycI`(y;=45>p~D{sLVpMPtj!+*bOzbzxM z1P{0G41Zo!_c-IQ(vLt0tjdjZmvJ<~BUqINzGd9MopJBSQnayb@2irx_t;+qWA|UU}QT{l6exlBI!u#$QZu9$# zdfg<~eM2nqICkg z7sUK~W9P47^qciPH+c5FL!6%_0P>~+|M%Y?5%t&X+v}`10D!t4;=Q_xO}Rf?H{I*s zyu&ojz33yW@`R*g|Ev~t)Ws+l9T0m|~m;aDV z{(2bVv4N}3+$&9gr!ZW3WJK0!nskz=8jug2G&ThkvgRKjvYSITYbJhakWHJW>tiztVm%W> zB{nP4H-!i1M@>)SOX0&xs>YGx>aktl@Sy%-b8mGajObEzINog)!e&%2U+Qv?z-?Jb z;9lX$yWi`(F2zJ^#@*H!1#T;m)lsPy8l~Z`+DXb>`$hvC-?8Fs3}hDGoIM5DlwT%1Q~sS-=QVJAFv6K%{&N<6 zzgREFmKeI3F;r%_%+qP}n zwr$(CZQHhOn`hhhK7H=(=>B;T@2e^zGwZivt&wvL1;EXUePg$$V+ozNxQ(7OytWOH z+rTLqY;p4dg5eyhnPa&GuVdK0`IFv=`SUD-3?u3t=>}o34;&U63_AsxlEVu8w;ztd z%0%Or_VQvRlDwmuIbBuA){CeDXLFu~T12`B;R}gHMsN`wS%;(fWD3Xh2KxIv{0w9d zv)E?ZXk5;~s7)U~Ek+nKj}F4(3rZ{^Y^mfy`&)`@Zw~P3Zo52+cs7gqxOOt+@ zA?7jC?1p^UvvEl(?r#eQ@{Ryy6*Yde5gYs@5XqWl12RRn9*8nFX0fNAONMzCBk%h* zY%7=?tDs>Q%j_e(9d}kZ_z9!i~ZpjUFb1kPQI(H3O^SP#Sba8v`w=V4l_lPgb_D3Me^?_ALHzWiuwNebiNR>y?K>H8Rzb%lXL7MVK^0fbi^h?io%{wPCUG`u z;8;Vcs9-XTAa}RjoHbyNY=%z{oc9k8d514}d`i5RJG=ypsZz5m*jeYEoaWy$TVv6+ z(emjK0@rViE~kS|crj{DlY51bnpABfOxC^IUb=g_IqUCIS2sr+2`Wz5m)3?IK~_PP z0pr$o%}|=&IaSdy*__BLi@>Q^W0>17igJ^mJ;*-15 z9(oIO1zRERTGtnc9=(Atg0c=QNobN`MB?CVN=rJ|gQY4K(nxn$Gjxa-8XQ!K_z-(e z#UxQTGaoepX_om2A9xg76u~u!K!xjiZaAwTtSX>#+}_LJkfAx?UU60Dh8>AcKC9C4 z=DM19@p0!nGntC| zT#JK;Fj)unrPd_v^6AXZ2+Kth(-GbH)oC>|W)5qw$3@&=tx9=c3id8@%~gUju??tz zf>D4axL9@G=eR{6gLc3wuc6%p8`O~nGq0}V8WC$o&6o>!NyEI#`RROFb4oQ(Br^FQ z75%V4eU|WUgW-sg49%?og2M0UWZA#Y%1C~4rZc#T_M8k|9hb7RH9+9^u~jgv9~Oi;G~R)^nGhv+MAh&Eg9|>BNVSpq^VOz7Po+cK1?h&S-Wc1HkIb620T}m09vasii(fXl%Rt#l#QZZY}0tj5hMV zb21&-C;;1xJDTHLqcKFP^jmm^@d|&V&D=>bHTY01kkdj-+9N%e-PZ~D>}iP zT)o{QDix7gK0n&J)0{71D&S~+4W;aAA=cZ|9P}&uY@(;4N>*EX%hj!d$m%9S(;Sd| zFA>i}wiI}$T8!$->RCnKK5H?;+@JG7qs6p>K~1{%dC+M(-Dn?$lzI%gofh9#iIkdV=AuR^P} zMd2CR7SXu_m1TA*x`%-+TMf)~UrNm|h&!|+a$0uu@KT#yKK4<|aIkTtA9!3};f2hO zR?gt(=Ip04H;M;e&XN6)p5@qh*WdJ9xTQOfvzkR8IYA<=E#Y|2%0#mAQzEw1xZmJe z;>%G(s|VvQ3jfK7Z}&a>yYaQOyOL;osNU#QTD>=Q8+%4r+hStF9O?-{sID$P#&%#} z(=h9?qSXHnv+bdGz^}v5^{%XxWGFuHusdf^ zXI9#I+FNdtov@0JGJx2qs$y>EOCqG%;2&`52BpjMXDWc=QG0HmPGDEfVQyZn${N=a zu!6e2Z+uc@s?-|`X;!Ng9v1Sj@|4~Gmf7#krsiKDAe_6N9n;5a3rh00S2lvK`D#{ zt{(AcWRd(~S4r$+Z#k7X*3J7l7$a0VR7iE@#Sq$stK*APN=5;Jg-1p`NKH1@<>c&D z4)0jI?~}~;s67-}=6^UE;vtRB88|Q|(>OKCj5fU;G`;QW<4)@>5YIYYPKt7GFx-qb zrBCa{C-e3f;jC+b02@k^bB-x9K#UP(KpVs-48oo!Xc3dtNM6GTL`J#J#G|*qWwb`+ z8kWG}>xneI6rWN7?zt_uBc&_*O|!&h(8dv&37DG5JW?Kh%okGh)8rzf?JXdZ~QI8<@f28 zBIT08J;@_qheb*y3fU*}P+4iM<%e)thl!xb*~7t?O2Q>-iAgD2hDqboc>fdnaGal2 zU5-BF<(L9|*T)yTyPi!dtuF>x9%PJjFa(ofuon*HAPSC+XQZPag!=cJHj0j%`YU;k zB1r6*b^5p!%AGh4*bjo(;fQ!-GHA?!ffrS5hhBW=!U76Mnln*D%6JPFX8-orCHIV^ zn6eNZR7GezgR4tk|A;ML zPhqA7r@B5ozaQ!=y4~`JO4oZ}St~(5F&#Gjx}R)*NTz2}Z4fh1zPYF<&yX1?8!?=0 z`i!@oSTBY%r6o%5<70I$z775s?Pi%?-;bu17ryO>TCHHDf3iQKgN7FnQTjXk^Xlmi z_A5_-bSY_yswT$S+aUG*km|wOV`G6w*L%&>*X2^mnEkTfA5~;r>kqXC_@o-Ar6Lh) z?BFNpReJmiy+0|T9GNf`rsmK`V_Z7Nn?$NiSo#u6G-7z^6k|R3&(>(LFvX8W@bXq3 zuPgGI1`q+h)cyYNhTvs*&bA?&PKi=TAvfF$5;@H-2Fbpcz_e4=nuDHE zjTN8F>UU}You=ee1IxcaXXM2r@i`NsY!XW9;`=yDWf>~xumI$zTh_M^+X&sjAb0l5 zn-X^E2cFc)9OWZX75A0L7=jyj_xaZz%cX%0_I*0>F%hzL`+4>?K%Udfg4g*E=ZhGm zC8cK9G6poaLfa2Wq0FR$Nm%T`^TY)jsow`yTQno|YO7|B5X; z-!3LXzu*_aR^0m(f$4ksQO~K#@LqDcfI*eCI{xOESIR!bZhOQ4F^oOSqd^=CZ%7zy zhb1^eDpo<_LPi*f#~p@@17<76g)lY2HG&3DATF+H>|-@%o+YB!dbTD%fb|)>og3|V zHnMYc1{ectw&?fM+h37|kF&eKzUO$cgI=^fd*ok}eD9vS+|v+;Y0G_~1ucAy*nm&m zKnwvVKRJevopb>8yja|B^KCNk+V3rAoB&u2dgI2y9_-AFEmV&(r;Zvy#u}<*hl26$ zL^m339eSuimco12CjJh}m>zA0h_2R5t$+mqC0N)M+{^2S-vcM_LvDp@q%zzP*%IW# z)z3ew55G`y05SG3rx=msDTYD`D6w+Gy{z|vrdt+|D6|taq!0JD$It+X3?xGuI(d#Y z6U_p9NMlG=zJaDC5;Z|;y?6ozXsx1Vz<`9#Vj^Wi=q=?Olt`<`(96tKd{pFe!Qlb6 z0IU>U+{?g0dhLu;%{BG$;BAS3aM_@OrND}x4&~>9X8;Sy*gEIw=D`d&`Xx$%j16L5 z_rM6f(0PFSh9%(#)%)_J0oa_BStK7T5D_T%PV#^_YwaciwO|-bs24$J`+wKk?OZ_L z0uYt={gzhG`!HY;2q^2-}f z;OI^P{{T4vl68my^@J7{Rqr3@26`*P1t6eeJvB78K^cCIsmg zXc891hra+IyMSITiqltX^8%cJ{t3o4UV#N}nDNS~V6_3Q2I>D3?)Vk1*!ySvtzW<% zjOqM$oOt`?TNgKD_={cqW*s5o6qdfQHN@oCS@oZ@6`g}CS<+ydhl#&hp^1k$xiPzD`mLU7XJ760i3K1v1`2 zdAm+_`KyyrBYERJ`)UI3tPXxeIav+sG(t@|Rn=3N?!bJYR)lE%aaf)U4S1L5oC)k}rOU*X!^{Sx<`Or&lU zfattVA}?FQgNW#7*gqPV`88T-*7PQ;9CE;>l2c9(QdMS36ng0VH1`Cgo=g;a;TatGnMBW+`dpEe!RA56(JGT7Fg7b^b|93k{QX4 z;|Y$_t&WIclJOl7N!j-MO7i$FEr!D%r*D(c@p%t}Z6Q74xiD0;5|*2uqqa6R77az+ zEkx5RxB|xVob~1t(LGckm$qV(QWT&&H__kt4tuWb!D;Siw3@h35M35YEsD6`F zA2%k{VC%Z_I%_^d?gStp9oqYe*B()ULpZ*T z!W(R+REPjor6qLMmovpz*NnKT|GPp$18or18rRJwOvbKyD?^l&7mCuA22wer|EbYddEV|fP`b&-yR@1J>8s!R&n7XEBAKN z1H<=`CG@85M!X8HPo;(RZ9DX3+r)F#q)Pw!QgLfEZj zVMiO5q13*8O189B`g6r}nE=xPXaBWBlY)~oe=X9LI{OWm8Wqx{_7Cko6 zj@>(V*|X}fy&6X#?|sr^r}scwtDq(Es3Ge~b=h#l;yaJOvmhtdoSUdvcxA@fquddi zl{hxl(s^C7th0XF&B~_X1zJYmYW>HlWP4OKzndu)`>g8U4|S_(qyP@PcIuDWZ5_wz zU3X{0f{sd%mVEaVJE&_3n^sg`2J}lLHB^xUcFZN|WKq4iDz&azdGcHhW?N`?rtfAK~vQIFRZddSLagAIU3H2 zA-g&zoOI@-4{18sva{=2<(fCUrf*-vUDoRdZf0(*bKq8^IBBn}Tbj#!xwH+duG5cQ zb#|3xP4ZM@tOINe?(vwDQHH_LO=yr0G zYqPX-r_utZ4325u2~IypV%O`9!(!b=GTdcC8MoW=`h{URODPUjO(jg`rJDGR&mLtm zwA8h`rW~8~!D2?YG-xKvE(tShp=SrV(@TQ&xp?mq%Wn ztg_h@jq8O_c=p!nEkQO`7@f6DQ*Fu#v#~Lo`s*O3D?~spb0#Ts_?rqF^an6s&n&8% zXjkiX(u=0~WaI8DQ^%YF^QWBMi{1PrO082bRwb>tdu>`)F3Wf-3%H?)v+6Ik6Mdnzg`YlayAtGhma`-3woiQJ5@E5 zsM(Tw?um_w3-)hE1xUrP-+T|1hFpIM>4fs??5~G=}Uogc@6?k9;pJ z*UNuL!>O$bUMzLzz-yH6SR7m*wn(;^??dlu3A0%RpVUE z3)(jY|MW3=t95>_vOSRp#-3Bo&Z|>Uu~Mc={rXRvE2}|jnn!I*H)uHR4z2XM;drl- zRw$t=<^8JaO&Oin&xMNYE*g68pq0{1t4UI~5H1|XCfm28>ku~?t2Hb(-QAjWQ@WL+ zC-aS2$&<`z0e09B!v~UoX`I%JjC(5dNSF(B2pV^0`c|o3V;)v1>`I~WC$2w->x);t z7oBUpiagHAOqwQrv~pI9xrw+oYlW49a^25G3H!2c-GyxPOw}bsd$W$Wo;IlHDUZAO zY7-)EU0sRSf+=*zbAMV%Hkn{(3Ho}6v8K8=}9h$#U;u8p^w-YJ-~8lm`&v*Sh|+G@4V z&uJI=#Lcs6RBu@7S^b{(T#Y#-E>|5hIB%-$m~hXh7JlKUFEs5c3tEZ!&ys_^BbPz?aJ!5+ItoZ z#!&mKJu`>#I#yLagzPPa{Odsh2AC2=6n-XyJ1MGDhaP?Oj+j|z(UeQye=WB@`3a@e!d-rx(3S9J7u%2cq)lAmPmWXkcO2H{9f$v!g&QKvVZ|7t7^jG?KcRv2Y zEyqxX5p}u4rNq`0W!m_bIk?HY&MYg6xpc(;w()b3991lT&XjbX+{l_+nN|V}umXbv z@oC9yuxf_jQm|G$44ge8@p1&eGE!+qTpz#|qhn+!5`6FHXS@&Su`CF=-ucLoaM@<(17U$oe)EUw!uPt`6rmZF?i8KvBQtJ!|Cld@46 zTUX+kt2mLtXgaPO`qU~FEP|U>N>{oKaZ|R*j3V=}(rXCI?a^FH|4f4Y(6lW(`5EZZ zg29@au>k>|tKQ5>p~qluCuSRWufCS;-=4~&hpG@Bv()=CTc+C4iN)4RpgpUL?!@j} zvY;z7vZApjHm{g8ies(E(WG|u5w>To$u>ykw{JcyaN}mJ<pBof{%uA-nEmIYde!L3dFC`IA&wD z-L3T!Vr63J>b%(fu(hSCRjSQ{2~lQEO**q2m?O27&Z^{m3Y%j+NblxEFmVF-E%g@g z1{qTd(IRQ8gy~bvZue~^p>GD#PW)6-W14BqTAfa%^R{I@O)KCSpq^BVwmo4p0r69r zf@#`awMmlnJS$2q8ct=xbA_h4ysz80Ww&0Yws$#hYS}u=6fIv@#QI@i*Fb4OD(3VR zD^M1SCgtt|{*%L=1Heq3jfaBWo@7Tq(OAwFQ~hX*>Dr;i`6JnFlBp$_;qLUuLxeds zlcfrE_SZy73-)PAg%3Yk_CgGY#=>z`12fK}n`D|g?7>qT-iv*06?MHUxw>N5+H(?3 z`Y0zwjW$k238}#Hw@q~&knahj?8T1lTneUZUD%ub`qfG0ryVw7 znBk0;YHH(8CP;t7&-x}rVq%+ceY#B5I%8pvYL&N5$ORp?p?`RQME=6rtGSOoF@tHn zbFy0jReTJK1Xetk*<5 zP)+>tQOEhdx$f#9=0;n2+?7!7{g{Ey^Btpz!y-S;PNT#4(9cRJH{EA=^sk|2yy;Kn zq}RXY6;5wMn^>fedfMe|W6y3clX!owne zL}lYpmzZ%e`PUWpe`}aH%pCt7v2(bhf8!iAAC0Sk{0>t>IxG$-xs%4nmj1m6MgpP3 zezTgyMuQy{1=8oB(P2gGP9#Sl=i1ajpu>*%e22cjPsr8?Ul{7JFaTyA6a|u72LIdI zU4S$Vpu=)si8CMy+}P_p^P zv>3S~g@~@SBbvm4auj3>HeD%3A+B}1(TZJ13NiTsU5ATnXfzm@j&}5PJAGyOzndeZ zDUC$kfui8jl}1p$Jt;BLmA+A4m5P+2uq6lZp`2$Zndk=3{?i7BoTrg~2k7HAmn_r(fn722`31eo(&tKe; zjV=*rwv>}-a(N+|imsyi(3(y*FF&fSI}LE6}T`_e$_x0 z1X|C#Tue>k8U5dnufKjI1dv&07NVa1hK@TVg&e=(%fLE*_c}!t-9MBA1_CSPb{6q8 zNA38gwUQL0pY6UVNvcQW`;kBlJcTw^L;6Eg9c(qTP@lT&3us}idmX@HPyUeS0mTQm z%xsaUenKt(kka%xrI6khwpXX5x#H1 zSv*b0J!_M;ImABmwmQx@AMueWrP^+@6mK0o9;@r!PcLssB*e+k6CU z0}z8!xH3LykEq>mQb#)4%??>w^gj<=Hiux;diZ~Vd3HlaGOSSmG6IR6|JVdubPW#R z@yj^c_VZ*?Syg|bDb}&z!=lAK=CNsFb$DYDwfh2x+(pI~zi8mSz3p>+$)%jfkMpl1dA zcQoOi-X;K%-(G0}qX$5V7~~ct00`)o*HVPB7z6oS5{G#U$lGq$7H06P05$+33hCNG z=PvwlIZ_~Q<|ewdZHWF?4ixp*Df@0w{;B==tyOZbUp=l*Z$ z!fFkgKV(%F@PSc=x4I2}buvF;2+o-#VYd0cUJP(l|ApPi9|oRqG`ZwHH}`Rh)oxJP z67~FTnb#uRrMFE;@}V7NtOzTs?+68;;}}_9jJ}19P0lA1k)#Z;+u~~a07xbaWCwn7 zD*uF^*Y-gO5T+(K?cbEg@`(h5e#{FL#V3kv0vS*-tciJ#oNX()+xC#42ox z9o!<1GvI{)gtbUa7iAh=4J2pZ%o#3Y14FYoUzd38oVC`J%Tw4k{ABnGGe#y0Y#$|-$W@K@ULZ%S6-cc#(T-r3ZgjKBFUwr&1ngD-F! z8Wz6^x$7bz@&Fh06ZuXHnRgPYk7hb|(y5OZ5wD@%Y4B|Qyyaj2S~%cem(9gi@a=^( zq~hG|`SE}E9dfxB<9PXmADOC29w7Hi+}~nQXom@;DObYLntb$oBM|5IE#COS7sp2$ zAT#p8Ie$OC{E0-t)*IXxSM&A-{R_=Q#|~@@IKPK}hrRY+y?(ZcwdxhL2cqD@9-y|J z8BSQViitlrpXMw;F}04){{-M7QS=wRkr=e1tNUNd*F>Vn8RB}`p8hd=E~i?@*2?Xm zQs06Vl2g~3wP;230CQjuya1Hx(u3f!#l4XXH|+lEzF0JJmp@@ z<%6%Tq3C5wc;k4jTxWt~(K_+G;e^8bQF`o9JA zEy&iph)e0|o>MA=^s z;r%-DaT7i`d{X=rY^6Dn9Vp6W%U6fesUB#T*QWzZ?E7?MnElAcyVssr z*5pXCk1+oaGz&!2wD^R#(*c$lPX4V{9PGjqLYufo6LB&bl!RD8_|4hg3gh8gf8g!2 z-D|i{>ag!Rz;%g*_-N`=xtqN#?uYx&{q1Wu_3qx{*y{bms1olc&5hQYWIAup z1!h8kkk89TE-4_kXrk>4>)>X7JqT}31U-q@3DzHG+D6d;d-_3S8R10f?1b?qXUV>> z^kH^*o_<$K&eEef02U0@>KLN`x4Xz8>kPg%aT-rU{-1MyXTA>lr?#4)l8}mS{j>oPN6E$XVsl;>pvlib2LcVVpBoSmmV6~-@XuDYy^R_- zNbSeYga7VC$he$a*(wNm$Nut$6aq=c(=-%lPPU^hZmyD5#1!AQ)qs>~wxqP=u;o^9 z()j}|KTOFTe-xZOH4yBW+{K!?Wnh+4Lk&Q_2P$M#|K;&bP>6Y^vh^Cr2JJ23W4-fFC8Iemvn1+$%mHJi$aa(0^XEEnWcP4wh$Jcy(ZeCjbY4ZV<$X zgy9q$LN>sX_=*OCTnxo(9V0mG0C+ooANU500n3Y z(4iS+JRIoyaR!oDhR^DxW90iW#+(CV-18a6&G+wDT`~5eVtlO{E% zI)e#bZ;jmxKnOYl8uu84J51Nxu}G~U^+72I{fFVsub~S4$HNc(LU!PF=ii43H(q~r z@YDO>S;a3_kRs1v>5N8@$g|lQ()dw2c_)EqhI+a8R{oA&;=`G+x%Z)qq=m3+@ol6d zZ+gk}l#TH%q5lG8`Q`rrJApEQhdGel5Qq-@`!X%1YMq0=s zszFNd(C+`K6ZxGE@p2obM>dISOFaRdU&)WJc{fZbC~2YV=QN`lBTt?MPguBSVosjt z)o}RD>b&7ddoU+e_k$^9ihNa~<{;|n!vjxN>yy;W2h;q?sQk0jC}U;FB}F46QIIdX zkuUmYNBQ|(yt3Ft0@$*QiH|s9B7_Y+PdDS+MRHisq`Pi@+kBTC+qsNT#0of2y{|QJ zaWlE3jARyxswW0+23Ddg-#RMZuCP=%Jo-eT*d9nUry~uP zTVGG#hPqMta4^E7O8WG{dgpP=zD9{O%_!oI_WZTX#pTw*v)nBsA+T=KehfqDXNleZ zi;jQNNJ0dM-KRa40o{P&7a?Vk#tcv-OD#zy=g?#ZGlKXtg4{!x_qSD9@i~|i-#n$e(=uVI42gmtc5#t!lJR!KB9NCPPRgC)PJ$|*}$n(>R zeN+r}uEqM>v42jB-#5a4X}Au*BEg#8O0oDMRemoAP705fzLw+i8NkIlCy+zILDW)N z!laZa(YsMMYTqe<{GKI^sPp!+w4SGsSK0Zu_&T&fdfria19wgQ(`s4beE~7MA@C&; z_0zHT5~{C2E)E{||LE(dqv_>3-8;*FKkl#SlXt!U`dVvM=GH;ZTOwr^-X~7W2dPJl z)}Lh^q{jEkz!f9hC52+tyWrR?d6WWU*MSRtP+AAt%cF6|#SSzo{8cE78)bWCY+T^O zdi{Q-m6YNXn_;Mmg0C=!f70Nc=|@^@ERaMYt{^HimKh(H9H(cc?pxbiuu@R~#9sPh z_)8l1dll z2uS6y`Z8m>O6~*y>LLbW418lxeF<38Q&IfC5bGeuk57yakpEAt>HZPxXRQB0tby?V zAF&Q8#n{fXpSrI-@K)))d!*6Pr!pL&9mrs&(G**6_$|dl+a22o(5;8gVMVvy#UBS< zBjB3SGcJgyT{%$9ZLOg7Iq+gSXwUQ2zo7}T`UXu+k>33$ce6!p-~Nu%8U=+$Ji5KY zD7&r3EgA~X+~5;^Ho*p{W?l$qkF~H?e-Xm=@unb@@DRuv(Eh6>u=H15Adv`wzQ(Qs zcmP|mlLttZ26)6a`|u#>Bom*89quoNM#3XKMPf`}{QpJP4X5{a2cxf030b$_x7u;P z|1@SuxZ;g8i~5kJ(#4jY)f??Hrqb7Y4Y7=4?-!OMRt=joaK&k-%%?wlK9k)C6THn- zO;%0Z%u5o70n;ZA^L}tRx3t4s-WMQhzlQaK0zzj$e85w{$^h8W4R3hpO5fWJK}0+- zZ-3f{v2)kyT^bQMC(hIN!^?j@*`QP zhPT!yeb-9RAFLnOmE71L!%2%f@_&1SVS>B}}*rks*A z-*2CHTD5Y*|AjY@b){osB^y)wvovpmR7N_Nm~6-dDj9%u#JhQBgMqOmjW9;%uy(tu zA7_wj2^cZ;Z@7|=A-G9wQR%x};&L&BL%TPi(tjc~I=%_DXy#ju7hu?cR-Rv87D#vr z|CF0(&AH!nDfl`vZYv7WSa8|5!nXF3v4|><6YWvOgx?;=j@RB_TCH_270kJM@G(+X zU<1xY2cOZ@-@6(?n8*M>LL;nDT$j%nIZ9*;O$^j@axqwQw%|8-n#iYmWzG~M7Vzro z(?z-1dZ|HHMJi-x*ZL>amch<JE! zk&4u9Z$rE>w`io7@bPyh8&l-?_)B`DQzo%=?+sfJaxO zMpEeOVRG~6X&K|{%_0&+WgXJBFGN?WNQ9(R1q>$iMmCl)syjq4kYkf;sDqX>9l@X? zh2I)TmbN)GX-9fr%aq03Rr=-;9mYb#m>nVL9x3L2&Xd* z=2q7T>&2PM?cVDbJwYHKaK9bz_NWPFCrkzV!H~x3#+>c~+=aI~rQr9E1SBXr_?MgaN#_*bUoh(%%OzjJ1 z$DZNXTrq5m4hqgS;NFZdKTsh<|4cor27#J1Vgkx_SK!us4QWYs+Ypu3?lU7>;J#hl zUh2*CV$nD%7C(xvsJHT5G?z+bTVO$pYIo&SjY`RpFv%ooF$2w*E6YH`XR@x=q#9wJ z*xl8b_w5dj=lzsj9-HoI=KS(m(E~(AO$xWMD`Ev}f)5IS1#mzRK8*E3T*{I+*Q3RWi zo_PqJU@O;z7HtD|TDoV*%2%UqVuV`Zo*UngZSi$=O!0I`iOZ>9IG6%@Wh=J!}gMzpf9a{A&d+%|%Yn@$=5-nuRF6M1N z%0#A{laUf`FbpSSnbfjE&#smVU73J*C}M^ZqoBeS^`dwt>P1)0>BrJ#chZPnniD~8 z7x#qMcXM8drmK4C<~QPwzoQiAmLp5bVUnG=J5X>F4>)+$8L)JVZ3IG|Ia}b<&5nX; zS}L#F`0{g*S4$76$?VtH0JySDju9;f1hm}USRN5|SWvt=o!LtmIWohE`A_t);w0fU zoJIRdQ&>NWgs~l^;Y1F^D$$de4(Yjpqt8-b30yBnHry4aa3~3lJX%*#7aF+jG_qR* z52xDA2b^p2na+_dS=w3Z?Af|b@VcA0Z^(^3L^`OZuFDQrZ5LzN9Sny? zs!H!c%(?`6L{qXHVyA@?Xc>^&O|n(ZHJ#3K+y|2$h@!2ONlqf^O=Z+dxxwl~|IPeY-R% ztTXv&uITKYg~n$t*C5BYh3Z1uL)j+vFdT6N_f%|Uz1lbtEC@?C>f+0>BgoIM@%1FE zaTkV|;dnU%E+)PRWsmXfGLU2mk5VsQQ0dbXEY(}<_|0=U+du#VH5b@A_}UE6(HipV z%Y^~!pxVnAa+-4dx+F^hdXT(ZAlw6PBE|LG!EYIKYO7j{_*FI$o9{EY_67zc5`dgT zYCQ)w2UGa8tO|d9^{waC@j*KTBBmn}IA955L*l?v$7J|4r~FoH~sB8PQ(#It|J$sjW{`wDcW5^eotrG25oVw6NYUD*=us|;hj&09D? zlSv2cStp?WPWcjtj9t{~+Gai_gQTXUd89dd+}W{qfOrtqi1;gm$*tw?nJ5*Cd7?X$ zS*?+~dPu5S-O4cy?Y;@PtmC2n}=NVpgl8sC?DpM4=QE}ENBA^R40bW-(WPf!dswh%fZ1e0J#bD?Z|hJ z6}r3_{6=B-8@;kJHUS!XV*;hmOfF`I!;WlX-P-ohTh59%Do%i$$SxhspN1hi7KJk5 zrms&*k-LBwlE#}q`ig@Om?z)Xs9Y_Ig^z@TDo3)G)i-Hf?u6CitT;Gw9->Gga=~Gv zh1g(!ae_rFlnI@-h4>zZHm4-$j8=VY1+wwBcwt46M?F_LmNTRoV|0b}zFg|vrI-54 zWK89jw8(ub08T6QrgbOs+!(CR8+Kx1f%-eBO#|^Wvlq0@yL~7fYG+Wk4w(^-L9qNs zaX$*fDS%Ge3w&cSY1Yt{fgSjV&u!U$retZcU4*q02E;5<-1}`z_AQMD&=w$TAimK@ zU1OnUKe}>laJ}!G9kj?jqCx3p+Ou?Vq|ABi1foIYIn*KJc8a&0*28Eo`lLraDOT*} zZCzUV_6amh{@Ia0A8Xf%cX%Vm?^(ab0RBcFj;Jj=SMh!-%^X1ehioHB{ZEp(^&cun zugQx9(Df@ZK07($0b{=I1h+W`mZ8OLfbn}T`Je-R^z5n(+UA&CXAJ9f3jeXfM<_y` za$D)`KP<|RkvEk*EiHskXJHI>GMcm9aw{;3nDUtbTHk?WF5(9b;eNI zw$iR)%|WLSnThFW?!{wTb}$_@@xOH9Jz%6k+ElR2Ws-pe{rvxjv2zN}EeOzUY@FD( zZJyXTv2EM7{x~_YZQHg^Y}@u^rf%Jbskyi2Y47T;+WoL!x>tSuErVnHNVyDa>9^m$ z_L^T%{cWJb&fSWmqSGwCZt10XPrqG1erw-I2;;}lJMSVdOHF}dz+ZXAg=aKj9Y|#*UxdlSzZ`0MO7pKAQ7}SW?U73PBw`_>-$3)_KL?b!IX$mJo?`G?-2pr{pBe8|%Xrgixcn?Bx^ObyAn(2)$T zx_bw7BzhY1Z%R1lk1>Q%i&R3V`NCiL1H6=P=z** z#acTGuBY($$o>jhWSae6b4cClc<|Mmf4_%Nehxb(81287^S;6B#Qr2lN=+7kzRwYc zv2DQT?es0)b=hMRP;&mvs(5JeM5eilb~~rF$eG^1O)P!!`7?UVPIW*zJFq<1<7x7s z6ZPQZJ6a=jUL)@T7Krd{dMyh3OnT=fL!5Jm3AT>~Eb4Je-{%JX^+o!^5-ap|YVMba zbJ5F@9cJ6-0u~LuY^gjU+M10;k~?1ARK#*(g|>Wf8PhI5lU5eIQbZF zr~fW}{*&(aZH4DcQ@@cq=n)CvMd|c=O6Kz`4{_kvz&O_Pzi4rfyY!5As~o<8%-i9%PvG=A8}WX%tN+xaE`LLg8>`%yTd z)BPoXKrqu-xNAp{3+}^iKmr{fKz8s&bb;8v{PTUiepkPo2T`(~nr^;?-LJ0`D(+jE zn=cR0<(#vrIXwXnmj^yyBqxruZ`J}|j`e5Ud)vkyoX3yz#FYFIb7+*{_eS7nxO=ZD z#_wBFu3{00KFWaZiCY~&k384cTJ7F(dF$pu+5YDjZrpPT$qY}4_am<3b39I}fV3xlr&m%G*}grrXJ7cKsH|Zzx=8`wgAIYvd0f z$s47@R8p#745|zIF1ORq)Zl1&q(9MtSkMMvz4Kb~2_x&_{OTXDo_0F5Zs$(yP#<^A zCnibUsk`-Whg9+v7wk8)Sk=*4sq;q0&OC-Bgy9h?p4YkG5Y>!R3#2pNg>hhE(8xpy z1Uqjgo9{Q1Bkj);9$(TwnN*!zhZOYTE4^CAc(X%A$o#Ha_nWoJ31J3@PDp!WKGdRA zzE+Dw-WLyS0oDK&erJnE`@3=Z{=My1QIr&5|D!?&bg?aAtYKkeER})r;vNKa!1LWa z3<_g(E$-C^(#5vlj!!`_ipm(-L$|AI?KbACa2h|WFwxC6KCOO1WvK)~Cokj~m_PZ} zKu_40L>worST5rCz25gHB;9C&z$c!Kg5)*rLD`38=SO0wgJOtNzFj09_PS- zMn$MSpPL@ZicudRBfHKP;pYtjCU^cABbryx#rf-B<{%6nwV6c6_;7x%(mi(D2|z?? zF@a3Xf^)B@!mODO-U814ohhI><^As{m>+2^W<5lLpr$7JWOfiQ0SHM0Bf^NihW^u_ z{erLKvovDKj1W4Y7j1RawLng(4Iz}B$)f?dqS#)74!~_<(+C^S3zSkBd8g12Sb3Cx zMVY>YNkZ~?hawMnq8=8-&iE>vSP(cE;tMkZ60SV}GwCX`qmUL*5)F^lDp2Oo-f~CL zI~yC=aiATm()x`4y?m_(idVxx1}pnpS9IG~@KFA@CO{(@s|0l-lz?J!+Ax%C9Kh&4 z)PTNlFw+)@f^i&u33zb^DN+KMeI`P%7Ny)i2LlZEYxi-|Ue!5fKQCOc$E`iA*2QiX z#9eVi26u+HHkPNlEHQ9D#70x{Kh;|xY+Ij96}n?p^k6_z5CUi(?N07i6YT<#4kSn_ zDnr#WLty=+u8_hmn65Qi4+xi3FIU{uh$=L~J<5YbGiRVeFC!(yF!~1XVSsTqFM4?oYlil zFuxYOch&^tMlg$mnD?QHVoYbC3m~vSy9}*}@Y$Cla8FM{wK;&mPRWenI82&{^Z8!N*s+PpdD(KE+`0F}$ z8a{Vh*G`=9;uRHy)gLn+TlC98{aS+g>Sy=9ZQK0lEqLHM8#oi~Q2RKx?f~ct(;aB9 z(R120`gSXLOOM*tW!l2R%x%FKF-SPd0o~Ev+In63h6lHGN9+)W;(iO>)(lIDqf9a3 zZ|(w<>%$B~yrh$#Gq8c zVePA>7ZAkM^l%l8-zOcE$e;$r;ZEysh%Y*W$%kXcB33_YNH=)A1DxNv3eLP* z5Rl4PW|$x8!a)3;{+we)@YdtYlnE|8Q3WS)*i`R$JtWL34J3~VMu4B2v=1z&aH0@# z|AqDb$s&n%f7%=D;Jkk|+Y`NYfsp*8aOk+OSF`u7AoTpq_imU-Z5iS4P3OU%0K5yQ zrK-@Q&^bZO+MUt3Rv8?w8u^R}zN?hOla(2T#g6mZ`h=lKj;orSh1o^X*(&?+Vn%7^ zwhS@MLLRbR6U27mM-__=cU2W(muuOfsTtiSKQv!7dC-RX(8l749yy^Y3zJQC5EqA` zLY>S$5M1Vhd@B+%#`L&jQdiOnx*x07-ZP@h01+W>GP_6Y57s!VcEfc32G-r1tfAzL zjCx3=rR&dAc)+k~aR7`L10i#LK()%>mvHAn^|qUGa)d*7n1Z zY7NcH{%~dVtI<0#B(l#OccgF~4zwx0J1IkCI{`yjB8K#J6Yc=6vE*45+nL#xFF0~n zZ<`>cyliV$agkEslzVIGX}q9;;OB`EU^(KJEW4m0o&vAz^X5)&o2b^Ijdhg1ILPHk zu~H6gs;ZOn3eQiBw8(rSrn);5(WQB8CvCZa`b7_w2>x#EHnZeL>Nlf{Bx8NaM%WumN5c?o?#5BIEzZ0IU_@>X8l!$(k_87@5V^ z?xJ>>MxpuJkhy!!g?t3bRbq`=*ZCBehba`stqzU;9i`6xRk%?-+iD~i%Nnfo^!z+e z(o<+ueylO`dd(&8ZO^c|AXt6YdCL;V(o5DnvKS!Rse*~PIf4_bwY#`j=~nbR>-@&s zP*s5gBohO2MniY^eDK%kUx)(?!cxT*`Lv{Ut|p0sZ^YZ0bkmG;tC^ zul8VVd^09 zI7D!%aP5{Bq$@M?I(kVTUq^}&1O7)oVvP*f_wK}23CMzTiLJLq}x>~gi$Fmfh? zSk$Bln|%pVHoL|x$j>WjGFaP6@4TY@IH~aHeMRp1^XP_iDAm=37>;N-Ss5Z|wikk- z^d^BkY8qjkxD%P}yFDVuNINp%#`m^8$ z=xri6%Wv{(ici?M%|ok}(kNtHQ(0Ff_8hEOQ3QrhO!D+^@ols{{Ak%gQn52&I`o-9 zHby9Lc~lG2;@2BOWl%*p80n?`9U)eWl!#RYD9gPR*8o>=8AeCwNDY_&9+g~=NERaL zpOzxFHOP@;frR;arO{2QPC!wV&QHgwhjimf%smUOIk)ih=sGh*Z@s;2F|?b@8We^x zJQ$J!dNs5!Yoq&Krn!kr8jyBYx5N^MB9GW#molekZR-0`^B7Skb)Z8flkTUMP#Oy~ zbAJmp;xuo{(Z?@{hYcL8H6Yc@92WVmc{oP{fYewc`f#fk`yX+a@XGGXM#PjPWA?7h zZ^?a9WHj_&LI655#C2)=70$%j2pwz(4C@1J;=eM_r;E4rZjo{&WSR!}1)b4F*$0TqBk*D;UoP8mX1`eju&`zIIiQdYH*IRB?RWwlkwu0irW zuKB|ODEpC)^*!qXC}0)qsOL%u@rqA}cDlM4{Th$Dtr{89EU3njteMtj({o*ADrG%t z)_MfhMS#E~a1U+y>(m;Q(#|~b*t{3D%JKn}z2OCO8ZndeP`33#Xs4Tl}qr-zPFC}1+S9B z%>lWHe;m#fH0mNWDBe7og_thj_Y!Do3ZZ)p2idN2ymOZYjAG?b0Xh`r>b|3m{sgEv ztqkVoFvz2PFqIm|~c`d69R7#o+~ z@tP#v>C^~2Uo0N7O4@u{6EXGGpP1nxCLa#CH99#1E-AHnqW5VkbPu5fIcT^Dfnk@0 zl;xc3LI=LMsb&X}34|3VD2b7-M)a`4k6KONByivlYk=+v`x{XFO9ZxZkWRxzwY~?b znV0zxy~DfRp*fNnjZra~hN<0chZr(|MqUg?|BZ`1cr~EGE{!@fEd%z3$jCfGY#Kes zUxz(c$FRL%?r8#m`R#|klit<_M7&Hcye`J}o9BzFw{LlLefspTLWC*7>h8Z_(!yV=6aplDj3NSJ=K1}%16`l%2 zHTsw3`>?LAFudjWb%y;9NyQilQ`oA|!sHDtwhP=L67o?A6EzQ0RCi?<*&M~$-z2(y zH356NV|vGeVE!i*6u0zqZ-Q#Sk(!qRCTk+DQgFZ{E)i^}Gm#wn#2!aCfxHFx{_@!2 zn&iP(<>PqI^A3ep5%UwCm1KrN4Q$LuPbs+Av1FCP7&hRMMMXb)P zXOhxLD+}uf8zM)X9IE;_T1Ho({EDOTsyaF)iu$AP=#M5;L%t2#n zbgH1jmEoaH+1<_t3*A=RQV}V%QYkLI!NNs5e6FrKUk&0d42f!r-wz)ItDAJR<`jk_ z9h{LgJR>_`bG!c^JawzT;eW?dt?f=^NZ*l!XU1|zvPhIqkGD$LpP**5RgcFMtN!B8 z`>4JS#&@!CC_VejHSO;3swd6O+w;#Z^q-#m){D~W6t}cAItc7{We%xHLm^=SWI)$9 zX|BI)Envs)k;KK0W)$@iNJkSJiNY9n)YKp+$(bVxO=Qm)dilcAO;fC|lr0j+AVLFR zD3GmWw-1_>xZu~?O7#t$`zcZLopIT#po}s9FGh{8>;L~?)bxnO{}o2vSz-VAFRq;7 z$k=~RT|kbAAVyg|{2+w0z|&2F$T&_?jPxs-B)`wWn)C zfHVx=uv~4z8QF~?&qCxve_+nJg@8}Jb^W!(^S+GoRU-5ZgQ8LC$yCFZwdO9-0RTyh z=s|^@R&O}11yUS!sZV>6t(yPy_3L6ie;NDL4zswPr&qrD9!w)RrMxj=Q+LFckSibQvuvgc^1-oTQ|RcjOrzj~Y|9eEN^I zGNP@?@nt9Ul-FKnX+=9-*|{0bIw2zw@^=-#*3^NIb4qw3f+Q^dZR!bAvK>&!%JYu4 zx-FM?s;6ak zUdK&8RbfoP^@{D7DE(>jRZbV4{HmMT$rk&5r6F8=sSo&3&ilXU3@%;n#$u0Anvz|$ z(>h4&Hi(^?`mFccYN2qfXj1+uf(~^SFP5p_W&yS<3SAND2{3pUU}^2f$vVeqqQDjr zdK2V-_VdQX1PGp{FAMv^xkI-0#fbWyP@%0WYf;hr6SG|m=*zCYx8X{3=vu+)Jy+8N zRw4}coItYCj+1_{X%99=^9AFon`C0kF}JsU=TilK*wrQka5ST@oP0hWW2 z25ne22U1o-ML4edtIINc6(;{@ zmS!dMXCS}mxh5b-f9BFUVh$GK`4x~b-QSHtB-mU{t)-S+RS{W4^k2`lQK9DQv#mpq z2E`9mJ&ykx8!!BsdwdU#bAE<5e0r{{KC@dN&HnS6KUqGzofV)cs+eMO=8HP}X&8WE z;^0NKq@%mTg)|NB$+-3zjXj+~MrTb%`8J2A832xBuuoUZ#=)O^B45ZDy4eD+?E}SQ zO>#8cG5PJoQ`=Fq`uj74@^-L!-?uaG1*RFr4$+LL7?(Ji0|!p8eZZHwC`Rd&Gs$Xa zYQj%v_s|)~iWb2% z_-x!oI)0%Ag|7eMkLgqbcPzWyA-L%?#ya@U}LZZ6zBtf&onTx+A27-bF0@4H@H|>zz5(=t{^kP=v?gP1 z?#--Ftovuy{?}}fR0qgVWQ4J&J$@80RPpH+EeKS)IgsPYroul&y|8Zo#HhD(&TzGq z2$u$PEbL6zfSlR^yN*xrq;#^g+=6B|`CGwsI6hZ{zmhYvKHIPkp91X0x^gU`%^@?~ zzhy63y$Q*0Yeb|FH&p7B+f_9Waf;?AbO?OywtWf9VEq;Q=>y~J{Gef7z*G6_6=LJ6vir3rFv3)S$|B}FozGR~qQC9nVq@ZDM zD>^N09{b5w>7jqC>kuyMO%2g~du|NG-E!_%6u&)SG2ZHDgzBa)haf4ReqDkx%by7x z#07s3yY3WZ_#}iF*O=Y_M5>QbwpUOxj|rYFb~1y98KP%t=azS-lNCQ81*dp`M%`A& zMG}U7KrFn#+G+D0oe44pU0VT>=^%tcJE2%-A?GB4Aj$l2bo#F3DDkwDlu3$`k(F?d zBR@v;3R!ZHD?N5U!%G%+WYPXBCjrH-FJYUHEDWg?q*@mkIv*?`fF#v_XO)RdvV+1^ z20@&vP^J#jM9bpNvhJH4IvN*}G?sKd8xSB@XI)X-R-Y&(Tg_KueO|)frzqBSsvr9g zs>iZ+hj$W4H|Kcz<166Xl3w5nz_;%7i8}bAxQV?VR<|l&v_F>C8!Hvhnn)fzSjwvd z^vw+V47b5zKhyg;5b*B7>-H;KZ}&hf^f6YSuL3!;Unvf3td@V^4eorKYiUFYzKm5X z&ONdSf%!enw8w3Ve_A$F4eZYzbLO=Do6lZjfUcW5O5E!Kj?~9d9e+LH&7zF$pbDJd z^^PwC$pWpRDOR#76ctMo0Yaug(@FQ6`CzPN)L#DNBxV<)(SxdXw%3z^%~Q#dp-RJM zjb98bQlqBm5xDZeCpo2#M2-EOvePVj`&r9IWn z&uX1B&&Q9%l|xhx_hu&#l0l+Hn_YUGFZpQOIYN~9)=DoKVUMHaN;UXPR?j)#rTe~{ z<&!gG@ACO&eudJqPJiOjU>$Fz(II@HmOwD%Dd0YUPET9rw-4MiTXo-f$E3QAr{2ua z6B{5Eha!w&=Cj&vH99AA&OSP%5{yw)d+;FljClUw+5e6cO(SVYo+k)CE;+vL{C;@W z0@nT*m$zKFnrU%gR750TAD63CLwK@J?HmJh=B`Sxo-I_dP0(gabZ#?~dHq-=e~qVn#Q zIYKhQpW}H9|H=|d;L?qAuZu&gGfDW6BYWL01JWSW_>I)CmMRmyU4|*JoW&bI%c_y# z*pDJEpqGCGZFJ?#Mi4!@P^s-ta?YYWqwoou@2RTuJ?HLwxA=GoDgI91Sh@6NuV;^y zMwS-La>g=|q*3f`xMABA#+Fe3=6D*-pNxLaWzKq*(y zXMvt3eesdBFW10KBeqC>=>VijLd1vS<|O+G0P3KtX&=eXWR2TivI+KRPH0XTHd)kS z2r=9RPZ`=~Ih1dBsj=_)e;M5lwV{c)JXBn}NA+~OkLl?-3Nxq^bRf)pKZ0KoYl5Z6 zb|#$4z*aJFm0S>yLy>~65$qzuO(>lR*LsO>ckNG;CqNeVb`ALT-^Vw+HW0WcR%84n z?nLuh(gy8%uVI)wlBbHSE4c;_C5wEq8_0P1$@yw^1IvED^kCl=^_O;CSO(+-2`GX} zgj(rZmA<0;3TCMFLrHYM({9-FA?2xU4|80XrF*Lf|Ap_q#ruryi7~YGGsjU#t$)W7 zc-(XaMrGQc6x1RF67`dhzMh|LfOp3GEVBd@SPf%F_1F+rPb=bV(W;KR&vN!BV&;$1 z{7cP@;g?M35m#h{X?4Q?sb67SCcSw8_9P+HgWcS;aP~Mp6^-yLDMS+OxEcOj#fPCqU~`L)3WbNi4ecJ?i~&e zQ$6r6=9<-mRI{K{KS#R01_C$F2}54tehmX2SyI$cu{v(J3DRd$1~QxadesMzPbrP) zLkIkFhlove$1=iOkDt>n?Uk*S=@6dSo5^|m>h7UsUFlbjAMKDI%;2c{!^GwU!U)Gu zz`$d5_|FywqiCd1MusCl*YcoB>0$t+P~(p@#kgb_9`gx7#cIZu_FebVfwA9 z-c;8?<*>dTRYY&vi`lmJsu7d{bL$VFd}VsDRB73i4qNi#k1DucdXrk0Vf>W3?Z75SB; zlrt1EKG=4ft;XT`_IUH`*p>st*O*c}h{sA38%%$bH1%sZ@K0oTG?Xi)^0}IFJQz?3 z0JMkyrf_@Ej@I2>Pd(ddU?q7RGaDtwu%!mVatq_2Lo2(Uz z*Oo>Je#{)3N!`qNQcYmSOtKmLH0uBAzWf z5zKU)3cEM_(2=^ortXG5z%1O@YCcYDc3vxT1lA2B@!(FA^HE`65_Pr24C1x$jJOuW#S-<_L z(?uy0=4rhHizC zP`wXv+PrBm%0=BGWsnRz-e-8k*j(RgQDsZNZt&A>{7&RlL5;V4EhCf9Z#a2g?!lCS z#+8MkN%DHpe+L8N;%TPmMio8$a?bT6RpC@Sp5W`DVsceRZDL|vp+aI&+Sb$TIHH*Gf<3ihIN!l% zH|%QDS@k&bBD3n03Ergn@Z>dq5%%1$kbCz-PUU^iQo?y`eN_n0nMXD!2Qcm&frDU^ zP&b|_euoE!fTw7EaKD$oVog^bNgYYHkxK~z7A0J#trr8m9E>V&QOn65#I zGB&E|B<0d_AEtr1tw=*OTh*g9Qt3gyv9sM$b)Bz)-$y8dv-%uSck!3>^?!a!ENU06|0+^gjV&8}tjP4ik zjs0Jp?iUc(FP-41!8?_w0trz_Gq#&sF+Fd1tK2hbqDuls;+XHnI2@QArQezBc~$EG zysGYf0X$f*&R_2z5nAurV1l!W-ItuBC2Uor%=r-)%daTVsX7Fa?$O~ym8SZ*qRE`92mFMi?>-Kn${iy`gLkdiB$ zRbd8M1wM}a6>#kN>(%1}%Y;j$lz8><2f} zB?7O00uerMtX#L(;Xh}dtM%YusRCE3dSu|4SYc#$r&C^bacM)P_855<%euWRWo6ki-VTn>bdvj+iRy{PRB+D2V1#jAt?AtcEt zqepMi)IPnTUKKJVjSwjz6(gnA9%Y`Lp}c4ysW($%xy3`H?m?|7{Uy?4Y#&q*uw}zx zrnYskWJ2lDJ2#=*L(v2=H)QWorfe~UHOrfP%mX*KfQxG6zF+2Kk{UT6buKr1LALAV zIMG_ZIpk{HW@Yfb)ZT8$LsA!j(}|=eQ>3Z>tG|5!`H!w3^VFtGHClS-R4h#hyWvQJ z*2a>p*H0>D$&fZ3>d7-741-LxvKpq&X%`UCOAOm{qsPQr-INg72y77;?DM8ldN^^8 zGh|g2e#))QA#{L~=vcLi6>C`qvaXIbv62%xI-Tt8H3zVbtfGnN(x#a^Y(Hby{Fs_@ z?hLmLq)GqHzpm7^PyL|@pEwE{g2Szyim|?R3EJYgY3?Rm=1B(~Uhz3}lb!)XzgYHY zS556%!@}UIOv11oqvmkHL?xr?>mxU3B~5H;5Ri?vJJrafN4@808m?Atvmx=wT%|#pRIlV&wuHS3Tp!jHj#<1iXX-oP$Lpt&3 zHv|ioyOC8u)s~*#w1z;xDeh$2)o*kcfu1))I=TM@GOK&Rh#Y122oOP0q-L_RuEUQ@O+@p zO5o4;q3DEf!DFl3SW5zdkCjrCF!Cnnhb)ydwO^a|uMU-VRsEysh4a?G_o!0dI;fYK zr+o8F369AJXh5hOu;k1*KlvTj1Z&<;sc#b>$NcWqx9z`~^mn&;TDK=gS$Pl|qNQkp zlhW1@A*XXRowq-!Us~!#4GKk@8ea5^K5c`%VDaOc` z^qvTJ6y(ee)ssd$;HirU=nOinWL&)AxRX+OA zr+Ylq?V2%8creaeu-#ljz_I5kj#VGJC*FK9t_32BzvAherX)pGX-9_`G%#O@)M2%G zvl#D!pIv3Aqr%UtI5Il+46^h0Z^Qp)@`Y2&kuKqniLGRW<#7W#?q~+#}~4XK8d@aHe3uU43z;D1kja`*5yg9O5HqWM^?Z z=@)S&lX8Mgs+ktC(0s~OJwTgL-0Yr0yJww6u#AKptV{kUN|9TNl5~MWnz}Eij+;!K zZ1;g0&MtP5-jDU`9xUIKXSu5MrKunh=;FSsjQc2%bZpP$6vi+YGpIkbAqZ|LIgQ1= zmGphTf$gz-dG)SlTOb)3CvpPysV&Ikh}!DXdLk!%BjU=o^7-7y>RxpyB~ejTwZJB% z`cHaIr#xL&Y6kl3_|Aq}%fiOIK`$AdV}qTcX0m;$in zhIFh2&z+nxHOf0R4o)Wg7~8i3x7ff2sot@#W@?4N?DUuc`T6s*CQK8yaKa^^;Zr+X zj|LUyH9Ik?K2i8a*04+vSW{wFqi5J45CL*62a^3p9qhad`QKciUzm%osgd05Awb7rQk0ka)>M0FWfh` zPF)Iq2CTiU8sl&{<*-2Qp{mz$?qxRwX?Ar%JO$~nmS{+4Bl2DWb`B#S%kU~_ASRoz z-=kmfA;%UA`wYr3BF)RituNwwb7CJZ=u1On;Tz4S}@z8mhYF8pnvMDnIY=stnLzS zmC)uO{r`X-p+5=eIx~;O+3W47%tz{A80^75LmQ=|Tel-s0ZmMCXkZ^uyN$ndUkB93 zH&#h~=UtDcm(cC5lUu%MLYxv_FFEv$BM5GSbETlL;$AG&xBN}~(p3pwTaHvckR0G= z#Ot}E4p*D}!3pR71~hw>UMkXyw8lkUM1(w|F%5$+QQZ702ukjN9Gl`8&|qvi_9x&` zh?iS|4lF@U<_8XSL7w!Y-}rRaq+B}ms_rZm`teDpy}nb3y^Gr#iVDwkwrt)WV~SF& zVTy2|&wL?8L|&5mqFn*s`KH!DG7fa5@Sn%+DT`}qqTOemSF`1bWVbw$dEEnB22f319U+p1c(V;jV zz?hN`XzQZP0dFiX3%|_`w@7BXey!?LkcT@1g9tpZTb*iA;`fULTaC&bbO);cPNa<& zW`*Y?Wkmv^AT@W_8nCk;yD~~~r?(tznw?-Z4%TiEaN23>w&5}jCqm7}wmbn3_sHgN z;R}xRvd0nYn-s(cISb4frNd@_s{Exz&eUQ=gSlS!jzI5UbOW*h+H3J}RV5zI4Pyx^ zQvVii&wk>HEs+c^i3$r@ArB2WFi9!VrN3|tK!UxK59#7*M4H47k7XQ`)sJ7W;pMhR zv3|xmsbj~?WdOd%wXeU?V2C*WRnW^37p|^Gjv~TAs=3rf+{XZqgwBYE8wU*ndxN|~ zm(fD}1?;W=MN~|vatmk8wyAOlu)haDpP}2Lx~<>x`i+>0*0ev5$LA7_WtrH9| zrv=h<79!(P6jQ?03M^#zB4Du#fYt+y(7z;y4n z3hfZf#Krw(e<64{%!PN_{#Q~yMy)0`?3Kkbi@o`-E6jW}Z zl2Qr17-c@mpGEz**bLzZIZuJEyb0)~Tz~X0J_$yDgl59Lkv)Fux6h~rwHTF+9iyIm zYD4Xa4mi2Y8-fs)wi%pUL05lA1F`G0qV<<^ia=DQ^3g(4{>U2mVbTNJU~8>e&$2Wp#<12zXW-E#z`(S1TS&wyGX2AgAMzs!W19yDg)w_tzvnB30BY8$l z8&B%P7KNrJDo%g+pv?3Fdx;OS7kq5=C|jbAt{iK#wgSbNTV?Ne5)?8Hn$h!JnNyk% zaABW@AjE+1fpOddc@^u@CMEblaB=-}~(6&V^ z=80Q?T&eHDeQJ$%5EWBU|JqT%2bR+;|iHZP8f$taL;XM>>sP79w?ucH@ZS9 zh|8PyNsbc+I|)^!lRVN-u*ic?B)~89#m@?rKAsA2Vs8ojsS#BATtD)2e4|}62HKA% zq+DMSwWXNG!~TVZ3nyVfSvSgCh7SG62m&?}xt4H4 z>;mblKf{K<<+K%)seJ>o7k@1?)Pe}Sf6GB2_SEsk%KDvo1tboU5|#M3&38xE5oN0B z9UU77bNNQn^MVV`Vct5!D}1rjn5bjlL++}=B5as8xU(%t7wy9#Tj(6Cxwk@!X)1aW za@o`4&=N(#3zdY7`kPy}6*5``!!S)c3PV8Q;8RLv!@X0>qh2SoV$j2k!BzYXK~B1< zMX8b#x;yn7P&*gigaUSvt2Wc%>SdWU`+>1`nWJ=w#uF}Z^6m0x-zcre1&Xuq+oj-y z9lNLLeGCi%3JnVP-~h=QJ3DGCb_!d@co(`Zz_h~vH5H<_072q@>BDE>330+&K!`F` zwE^&K3DMh%`Bj*e6N`{Ut=L((A>86f&J5%JHSBr`4U^QZc^u^Q@b^;eBv0TExwdhr zRGNcKy9Zd*^R+(h7S_Ub?BqG4gQGxIioS_uoOhgzmz2#8+pbKQ_TO$g{kX5XbL;@t zRr$T@Lga)>(i+H3M1yX0Q=0wPK7FnGI8^cPy{?tG@+=E0ma2s!n#PDM93uxLSc6EfD?RsH2cRGlA_AzM22vE0ynlW@NSi>+ zlnY?kDao}FK)>j=j{M%(s-ayI#70gvzud!0kmPMbrU{qVyWGt*ua9Qk6~t)7L zJ)9ABafVXekS3eX1dW`k96+aU*4S<5Ei3KKZKbdg?it+MfT5%A@1-AsN6(8q=k2gP zIQr1J@3`|JRP4_3lotq~pXgX8wsaA+9-KNirXf7d7Srf&2=jCVvAic`=ET_v1pJb)5Uj$Eq`GEs8*X%=T?V%`k_D!3iG zViF~>4r~(xRlisYKdM|TNCttWA{jqFDSvf7@6>b=1_oM-$5T%yf&T4Wo@~e&K}n9C zXj-1UpGHhl2YHBsS~6b$$G>w6HY`Eg6IEaDkic4XikExwjWojd>@jGpUqmlJsEGXe zz{T!So97P1THI*)T8qAMqm>ue@n}Y&%9!}biuT3hr8mlr|;8}m8j z`)PpkG~y_J%dR9WZm&d{w}_G{IN@jotWA(Iq#&*2lgF>4k>60#Ets+T{Xq!mGuUwb z1V}ugJCwzJWX>;~w-bnHn-fF-nTRrjlyLbu4?a1WW;AsdsYDKaFiH}cd5>qz{Yj^E z$}4f#ZuxK0IX4`#|7&iAyl2B}R^CUnr$W0exX({l9C!d=na=t=NJ#-2_8)MvFB(!c z-BfrkwTgzALXM~Lt{Rcv3vIVb_x&C$asIW&*A%M_lzcq1-I9-i%JA*^(dO8}A8Is@MrVDq6#>`$u~ggSR6vq@V|R>9 zKb(~DEjBUg7j5I>l7)1+;tj3Z$!o4L`@hX}eTTR+kUWmV9S2KKGv*;p&0#nn9#>_D zONKqczofIjGy~U;rRH0-x6}G6(G!pdX|yMiH08&ksS*+@$-%tpDQUp5vftjSkL^Fs z#qZi;@0TK@8|~g5vfCF2bS4*?J#YiuBNaKxRctU-4m{4b-Zw;ZyLbqC-3R9R9}eBg z?Gh>5MpBK3$p*;`I)nhJ+%M0tM+wdorji?5uvfx_ow`6<{ut6E$t%782*N@#sPO1R zF({kjjy5I{_w7G_T~lH7F_Ro7-wKhXC4x^AiXkQtr;g&NuON<`pETmCBSNBka;j2C zO<_>xm!cy^((of*#zQk2YzEH*pj*|LnyD6sMPiocuJ|KM1}{(60d4+^B*5%H8GckR z3i`X{))V$gv>Vo=njzNuQ8dg$`&CRbDDZdzb;%;ERZ|oA z_A)K;;?QR(xrt6{Yi4f%2PUuElsgPQlIZU^b8;0~N9(WH8}n~D?P~jLnvbb$2AP=l z(bULP8vPV~KW1%N=b7J3+>d4f+3&dOZ4LH9go%aHY@B+2ja393D|fnVZ7ilwfcbe9Awa|Du) z$i2|C$r~<~936bt)Y0>M-z1jbcTSD?n14TET9q&HV*{QweRdRinbMU31O@0=!0eW< zA`mpmv_`RvzmaZUbN!F&bV31S7xt!Sr@?qsiKK~@Bl2&l^ajf|tT%AO6u3axWDX!|h0ks!#O8rT*A-Pc)5~36L=tdwN~Q5|FA?nDKL=Y9p*4gx z+6hicH@gpeO1M5fpiqb>?D?CC9Fe)4W2@-ipbgzt#~7kOg%t?>j>Fz>v+Ide6tfgQ zy^vEJrvaahoH>|;o>RbVB*snTvY#y!ks!N-I&y}5X81j(o31|o1^<|`spN&efxVXn z?wk#6#GfC)p=zpn#ITGv1JKIwYrTW*R}j1|;nmw{9Am9R!I5->+tNxOQsCcu0M zah3UVk~d@mWeO~{b>n^c8)z7q>wrB`0<5Io$$qS0tz>o2hMF-GzP74zG~X|Xp`O+- z#?DZY=FBgZI^B5Ar=qY$Y=aRP8CpWxEuM7Sw!?=)WAyRQ(6@u_CsM-e6;~#7#IXAD zq%Nm@ZEvwxS%}sRfK$ph6|4@TBHtei7AahhnMB}w zu;NM)H;QF)s0fR|#4OwR4**C&x4(ACdaeK{Dg+o4Wpb(L9vff^uL`hPL`6v)b)39t zbSLGpfvOYbm6?*fyvVDvY)(O|zQ-Q_I>~F*oecJF`Nq2PxfpM7hGL}gPktU^UAsZl zv6`8Z)AAt|j^Zj2O?acE%kiR`HxU2k>yS=^a|~@a?YjQ8n`t;!dyK@JWluqZ0}5m6 z2vATYlb74j!I8_uScFk?-qfyF)<2FJm*qY+w%qY`H%)~0t zk+7@hM;o>hm7Jt9S{g94&qGU^8VKSLEyK-{$~%WwIaXQs{&~GXL}M z|2MtfUZ1D;woxyFCP{3%dg-5Ob8h)WR3bZ8A+!Y+?q>IBR%cX26VjwFd>*#aq`R3(WKzaYWJ{3{se4N*v+GNTw;nsDk*L&Et|6X{Az zFHlxJ>Vz*BQJSxv9!`sgHFVN4#C7i+9hSc*@NzT3zH7~J-jEDQWT)*XO_N2PnM5P} zWQ)Y;+YSXZ!TF3DOX)_#$?4Z;P;=;IzqeHErwGeS(okr;0i<`~$(0*nY4fNNkXNKy zmSlDqMNM=1@fDWkORH%)WHdeK2cYP(6+YsB8?2=DJ6{)MK zB7i2-S6+y3j353$Q?A5`p3@+Ey#~PPQ6zR^{kNbjPR#;=2uJf6x3P@kV*`zT?LYz; zg%XG3Nw-Ptt__E5am2pNn_QZJ)Ws7vdFS0HjO=WqDZv~%jEBZKsgW%W@ir#XTQ*8k zLE~A?G}iO=eK4CfHcyw)>1FFQqgjNxEo zBsXlf3&1fQ48W?>no+O~wp}7-!TphX-GU0#bfWZdE~!?k!ElVkuV;RJNw&Jt%(Yl* zz1P!InOV=xlCQ)8IceYfs}ATNf#0cPxV#$-S#qNc#+nSv3C0wIfpJhBb2>iyQuDq#3vnQd8-#=_eBwP2a*yg6fi00;?@` z%d!7qeh<+r5o_tS#D%g^=3op~*A+gE-uKy6>7z?35PyR<%_)MMQxULfjH;rNHx&+} zP)BXA6bCp))nyDgf$#&+zE*}<(V_2Qz0A1}Z@mc)#;_{6YhCD8?3t6xTIhi0f;@4n8q1FO;SHcdWk|8$q{07soyQs?sE2b(| zycl(24!ggU8@K{0E-t!RRNUNzR}1J>bXzV|2bm39or8SKCvTpm2`eZ%FYr?$fMrc3 z$4ngpFV2^o<5U{Unp?&~S0DnTj)z5uv7&EWIw1u6u{jzf%sh-6`2uc&vKZ2ke(;_m zvIzyw=m1A7jV8%8&CDoyN9!_{-JRL1<_n>Q`w z*O(TtUjVk}2)c~ynj=i8WfS*ustwiLSe>J4qRB$T}KAPOLY=A<)2jJwqjBy`ChAT8QemCY{!$ z3Ry3KHQ{Ks;IwEAUNCJ3A$!^Z-35SI6CRDxMc3=&2qOYoMBwH1s5Xh1IK;64j}6q3 zUq0p({ayC;m-DIJx(A>v>kV;YGSc=3yo*$_KqVDGL-ncvuINMf{#22t_`gLYSo+R1 z_#LL(r~BA2Byo>TRc9(MvYafLX>6s%Tl(akci#|xsSW@RjG>ac$VN0O zmT5OTtv1@}Er79#@lI0Krjbp;a?^fZK^O8`Zgjfw6n zsbvcV2cbUNBLz>1XLl&uC=_>m6BY-Vy0jbTFJuc)g^2uY(XtDqH7rV#SlTOWhUhFJ zl9Om?v>-wy-r>{sXMVH5j%eSb^C+nU1|ES)l0f=CTIr7fQT~F)s~Wr(qhBpjSrIrN zS|sbrCo3j!lM)mOfNhLt15}SuZ9v_wQ*`ZQno_JEmSZK2PlYY~dG>Z9wUfPdTf_`s`A~^3le=6Vog;_b#Zx4l$Lq)!}qmttow>YI$`h@H^-XIE>JA1=;8?KO|0} zb%K`)99^~*u2krjmTMgCUl~w~5cC~T_6_1-W~j7na@4^JI{TV9VN@WLuiLt_36cjXBCqPU;fzp#-ot=08iuyEYk@N8|=$SOl%OCt)bw+3SRAuB_q7wLT1vs!H;=3-qs zG5k|H$n$wQZ|Z5%2RB-Gdxc{k!~>Z<_Q8UlL;gG=JDHJKFti*6go0o)b#R(z!eNbUx zRb1l+v?TD#_+zJmaf$YDI1%U=Krk zUE$~kg~zg|RT%(S|zy3qxGjKNWMna zh2KP536k01H{>Mlr6G=P$Xp(42z(vlTp;$Z-GmBW@e#rQ{QJKzoY?q6G-a1qo3t!> zyv_9zvXQ%C=9c{I{UepMoK1E*BMq%0XkU;#wBXJmSg6eox(vzQ+9X=#(u(pE>m&^; zaDr@#95dw({M@WCpQ4vo6AyTzWO+m+&TJiNCwk>A^@W*!Ex%c|^;loIC?5SXVfT%) z5o5BdD644>JwH7gS%FO}(hyb}?Yt;nsMx6zCBuh}bqHLA6$R6*#=M|wI+Nz$@bd`f z(ZXA-r)-)ZbB1d0f;zqZvqnzL(F&<+6EYqhngVDr14i-4lYrwQ!W#-#Qo{oas$uAd z9dF%MyWNb>EBB~!H?9-M+(hX1N(nI{_k3G5wWx>-Df_fsdKs+5iLl=m9*mbq{?H3U z(M!f&`L;}*c@YgThV%EjPxQneaf%YsEdt#?E^g5Bt8NVoZeoB8DmG9k%9?fPEaZLJUTvXIx(K!&fm&=Paa^f5DQ-wJKl{rQb*Z&nE3ZYW+jb;%CL{>Is`eX{uv*CGDU z8{u9%qr&Qhoq1xo4eXS-|XyNbi7xHtWPz@7wbgL0+oEK@qPb0g*4`PfoBwD3MXiz z(wKHw<7?lF)o<8k2L+gEHslJO z!dgI3McJ_%o(n1Y`aLI87;o7SI(;4G0~uj$vXpZ{2U{k(p$%;y3wXPR2hobsOVd7hJ0b26NLJua9(DQ6HZ?m(0d zjaDEc<{vbLAUT(RC54iu1BXGIT(5K!b-gM&`|!*)&X=b<_E2+<3!*0ryKr@=CvLzn zB5TaQdx1}Pb8F+weKBNHvQPyGN%?d$~UOsh7JLGG&i}@6R1Nl>4 z%0Nb5mzbPlq{;qYEHm^xuSmnX=+iP|^G6$rO*nJ3G0)UM!M7-gY0kLf$euBow~}!| zts_`5WErSd=-SI-n&wXm!^xZ|Noh`hKk1}kkAK^w%wInRTD~6EG#+d~Ryocd930J- z#9d7vegmd@g_)Fw*#urxWl>6lnfn%61Hc#Hc@x>AFpK+R$yl23gZUtaRwa=&jWiC{*OGpjYmG}($tA8egk_s`t;NpfWu!AZaQK>J z#7)}Xo1Nb|Ub+|><~2&R&WS8yT*kx-#|opmtdG z9hwIWfu%#VBGjG`*%YUG>eUmXal*O-v$txh0mb69`?5Y!4Zjp5P645!PY39^V znF$^2D8@DffA#a3CCBU&~;)D4Gn?R4X1*}ZzdiKMxJZhJALKYC+x4n$tetjW{ z4GGu{>ng(_&p2PsBO47I_uQ6>sEdPbG1&Z3?(C)I*XKEWo-Uy6x}~Z!{omnbHoP9B*X`4#5pUa|Jm;dp;g* zGCCc6En`2v5o7xZhl4Q;5G??NX;}jrI*>qh#U=yh(Z~Cx21%)zvO|O{X4n4!n`{XB zM~MF7jxmAOJb%ZS-(^D0h6U)kaGpQFZ4J(AfN#f6&y8|1PT|R%0s|o4DL|mYRuKn3 zB;)dl0kT8q*r|RWS-6=9#uvAeLg;gh&hloevXAfO5`&4N4YIisrZQ1>H0-NFPK^)wdCP6C@1)d4YM3 zY2TxWtOWCDB<}!eEk5l-{st>Lq02`L*lGH)Zq>6eFmZW=+k})@Ey9f^DZ@7Lu_%C{ zU$pWaPP#LQv40|m&SqSe3*r~YRnws>rUm&QEn+H>-(hA4T_>YsT*U98Uc5s+6ZKr% z`JOmsMlC}|H(+Jib|;`M!*|<5NL0dFcGN-kqcEazX*Xz?niI=rEzdKeOat~7PyyBQ zQF%fn2(3{F6#(&L*h4@zx}%~p-k~K@+z0|(3v4&Ky0V^lPh9syu$0=qS=#RLAbHv)@omIqIc?f4tZ1Rc& z5T3B=5vXEVd(H_0h=RvjxkH&nGpv96Mzc`zr19*Dl9uCHEN?S@o}de2Ud1ZouO#*u z>ula>l0f6HqpBKR53fD$ zeI=`|*MW55H@}S>ugUCwQ$n;UddKwYUj?fML*n9g5L-Q|VoWX902p@U#KNRbEP?=H z5sy&uWaUFGGc0Oo1@Xa90!q{ZkNx=AhJz2tvb5vGf(rN!C`u8aLhPjwtU^wdM3zqz zx%9~eD^_@*!H89gyN#1yrScZdf<>rK@5>Q@+D#NSbRKNiZ|%Gw zOmu$8a+`1kf!F7?t~1RI&?i(yEs2x>w0)(R^ zH}syQ9p?P;;CtuGoG^-=X+u{8s5B7z-lr;CeJN(z%kN(B-UOxuU9RYFhfwvHCNa1T zJy5||88{tDJ=HT$(#sgd0}}|Y7IeYjnNadtJRczUMix5wpO(FJjJ+2O9=%sceUGp} z5_=i@I8vc9^DLj|4Yc6K#V6p25+&RB;?6 zoOH%kVS7AxdrZQD6-jFq#Aal*gNqh$j}j(Gr6#kCF00dUSUF9UawgW1+}Hvmg2lfC zYU{?->KWZRnZ8qf=!6l5^&H~PIDQkR4ynIy)glKN7J2}M%^`Pp_{Rk?zkhy%cP!pw zdqY>khe!OFk|v)CsbrBgM}J;g!oL*>$$SIYAa^>md+rb?h13_A-WTC_=8E?>8TeZP zLM<@7KHUud+S`N<@kRLdfBl@R*?cOI<8|g&Y4}u3`8|ai!?`Doy$y z)>LCsSm3aoXpBOm77k3)^$nK zrO7yrxAq#Pap*1zt$}E#1l{gO99D-%Z9Cj|IF#)=nVrNN@gYxJLdSQnanWDL4m^%KEkH*P&TWuK9$i*|!`T~~Bv#OEP_$ij0cR=zQmc(vPd3FRt z;B{dN)Qo5Ij3yb7J2!gcVywqGry4I|J^ubP_2LL78+@uGPda!f<1o9|C#ttM4@59Z zxs4$H95XFCAvy)$f!W77A;p2l&MHSqPO1Pc1SVKsn`-PJFxkP<1stI_wSobcUK9+b zmuh+-Nbt2n$+_v#FCyQssZ#bYG{5wW>t#ngg{Ps>yflE<@6=xzMzZM58{)APgTX-A zu+xT10PHk68k_ZUW8^cbldGDu+@f2R567ywhDsotY+lb9Y_v=ka-#f+e;$r z<4ij&$rR5N)~?0{3p}4Wh?7e2D^Nw79wWuaP7tAJMRo(u_Qz|?BOEQAaR~AAY3M=9 z_;g-k^U`#}lwfFrq+_w&4NYt;?>+wvB+=L&(6aXxNc-}v$RQRC6sW_CTElm3Cv)LE zbzH&+!~kjA*jzQRAcBp2Y!mGrPLMzfL}k!e@RK_HR2+VS0g(Q+$rvrqZukM!`50c3 zO#n=X8&1`w@#3;Xj1vtDEw=X^z+8?pb|1=k3Zg(vgp54^@gN9HY%P(eF#o}z&!X~_ z0m^*K&||MkqE5BjKXsu?JBN}41Ww{=Cxvw># zu(U!p0~_ogy8f5Hf8Do^HKik@a^TK1)k@;6_H4hE^`X45lkNFIm9)b$_$lUh(mEv` zPL{n-{?QMWI+BuHY4}YCKZpCh5LN*BKn563g1{w{PzLlu)$eBnDpl!YmqaROqCUAE z(+94HD5|afel2LhZ_>jIxzCF^2GsOHJlhs94|0wm3%o3|3Fipy3}tH%Ds*fHM8_TQj0FIrUW2%k&@xT7-(R zQ}>|h;rA;|%cgqT#8X*l0!M|+9(QX*5BvoC8-3Y@QzI1a11;wC(Xwr_RFZz;)9@4# zqRmyl7(YMi)hb^eegof3WY6%+E!Xpdc@0z_hi|6OClsLLhL2Id(#Nfg{2mIl8I^-o z71_*#t@x6;$r>V~k3!nwQm_kU@#NzR^pKBo*8e-5T`^4AgtIHITCmEg{)_(gxfAuK zgPr4aUu&?SG@$D8k`XcKbpQPW-}EdmHh5lbqkU>({2B&6?~N*YgnN(&O}Jz4%nHKQ zgQ~v5+4EW&AgfbwO$?lV z-oD~2mMquX`JQe=V#FLPs{D3C`W%z~=WIxGHl^P_Wa%x2{41NK@zYMm8Vh9Zkg)@T zFa&hZdZOpAUPhOAHm^>ple{jOd1fk?aBB5#7wUGQzTHCIdYmVR{%0tU(_lJZWR|;D z@Fyvc37FW-!<{{zt6NHzo&D{TpEee`{X*8ict~Hh3FO!#Z zhGCe~$lxiK?{=kbSL$!ZNPJ0IdSkk*U;(VOl<6xMBI11NN&mH{LB-YPu`@UW6q@yv z!7s)5yHkFP`2t$tbXC*&DbyRz)HzP)C#$M_5dl4p^_S=Hv6>r%Q4zlgSGIEeU6=HW zP~rM&k+pTSLuG~*57_BWA zjB&yD%ulGuwK6k&!0qHMm?gH+_bz9PlKl}(lTMBJYJ>isCe4Y{S)J9GG}Y+8vQPB` z8Uitr=j*)cFxkoP*-6jKJVT7SEi}RVuI6pn)N38-pEJgB{;;#z4<9J@HW8nfol0m2 zZskQ}>h}#6`;DwXp@{($xMJCl6yLWUxG)?9u=XgFDg+|+oJ`LR8>tEzFs33KjJB2b zZ^0g0X}@u4|EEB&49zkM_f7G%_8^dj+`!?cBFvu=M>~Cl@d3BVw+YRxI!&i;MK7J% zeUV_i*ST!oYYDtzqBzI^jMoHA^sO8i6&fMrNEa@~$P&?d6wq9@de2tx`G9&)8r|o8 zH?_ACaP*a5bWhv63CLA&9j@nh{;cuH@$QxRC-bk2=C@i_?m|Ni3V2u5r2DkGA{K2ilMRV0#=?%f$RXv0AUKnD+&lWc~1)&&(3>9tK<3pU4vF z4w7>M%L@E!K)kja*>)q_@J9BpQ~>>XmgUSG6#Lt9?8oHT`4cC|CX**qH#u>vU@Dxb z;NZ84^j7}7r~H{x6Mp-!-wdLu+zGslPiybCV>3Ulo4ezBcC%B3TD+hJ(rV0*COEhH z_@^59_b)l0^Vv9Ekzh{ZIYVaoNfEsDWo$+82Nl5|@UZwTLkbMfsx%!MQE(RcUAO84 zAKDrHRtF7~1S8-ZTD+Pm+0QJ3R)QB%KX|cmGU9`R|ieheCl;msJ zv5Y7fJgdJx_hLJnTP5mUm8f;|={~uZ6$fs{5pY(?8t$42;;uZ20@~Pri2WLSh z@PX3bGGW^`F(=&$!Hs3%MY0!1r{up{)9?NK=EtADWg33)JnuR@|MMH%CA0{vHc?BT zI#%F(%QO!?LK&GAVD(4%hah4ud@z2eGJePFv@{<`fxW|z@O;!vKGp)ib>jzB1>Q>j zwmh4>`{%bP`KU!y_7HW#b`%9a+kUHUMMdwjZ9~(){*Oh}$}lSTZ>*r5fsdpC!ow&2 zWy5Ma_<+?dh7pi_{8_l+`3jLm{%$zH zhWnf-?g=9KyA}F>wf)eAjM2Zyd);TkX;$;Ez-d~M+jaKsh80Xc1^nUKo?3vx8hwi$-mn5L9=&%dr~cWji_>Td2xP~*GsBu{^x)6mA_@?!S2-#Z;q~V`E#Y- z%-tO`myG5g6x7FZ|G0Qx=!@k-qx&G9as}q{;^xGE&gLbrBlqn7qgg7P)N@ar^QU6& zLcs=^P`t~zf!K}Yrp_MqRQ<4bS~@yDXdg7&K~6rdRSzBp{mWM1Ue@lOcdstJo3p#T z(y5&sTGiuR$v7(YWu>iWeH|M-ZI%N+Xxws5({P(D`;@z=o*%%Ekt#pk+sT-9!y$Hj-PeIv;{Hh6h;`gGg#o<%*Vdk4``xqlj- z*No%Ca$axgmsgtA@8vI^JSi{b@59zbNp`sF)|sQ82%V?9ZaKQiKXl!Px_puyV1q~D ztz#FTn5W$vJ=c;5`SonJS5jstOA)e{2U!*->*yRNs|eR``J-m#pwumTM@?+7a&Y?i zSTdh_&q}G$+10yuXBV~Gg4OH04`p}vfhly`2cdrFwo29wb2s2>0ap^v)RGret}5Ac zS!iZ!*+$7_pDt(*uMfHKG|F8a)vLSru3oRzijwnaHqFaQ_nPs2!Ki4B=R#ko$%Tt* zg%b}-;a%gYsr3qCGbldE75DzJe{*^UhnRN z>9bP3LNvMRIl0vqAF`pTa%ILm&tEWJ`NX*9A9B82bIzXwpLOXh-zcnLwsy}Yi?yU( zwcE*7v(-|CJGv7)?pdW#$Ua7%U_TYEvQEIa?QTz7L}zS}5c|GftEk;Nxu*LCSRZin z{^01j$XyL8%tiNZAPSY6L3nMlUFKQ2s$JFGGq$mN&v=#U@$&`q)MN^e#}*xO>9Qbq zdbx6=dlKHAMJH}6>$OT=t{SKVLAw=C0)E%aInE&0+?|~SG|?8m2R>9m^W_ZGo)nu;!pYOK-OS4;^_pl>;n2T&GJU0b%Syd| z_;A~-*M$d8 z8RW|j_yls z@5DbXo|o-P|DxPGJrrc8c2X{MLRq_(&WnZewNh5=kDScXS#I$C`f2+?cr+`;QXsY4 z=e>gm_PF0Xtb~O#rFm=DuZm1AY->j+7op@a&voG}GDTLX@yC)Ugt;usDkrB`-DZ#S zVMi0oxlX%M4cJ4b(J2Zy{G;R#gf*fYA^egYFU#<9l@3|;kc-3nD+*bU0<5D8)PdK~Q zc5lyWJws8>vRS34o?rG#hN&n8Y*0}^1S<{2=sz3&Srz_J&&k6INN@Gg_DbFRg3MQl zM6S9ZB>JTX#w_+2<-FmLrNkNcz58ypa@S(9!H4SOm2pwNsI$I)8`SQti=yqF^Y@jD zGg-b99r^62?vzgQ{2=5W`I_q*XQ9$+-97iu58HZh_t5Z9*lM@hKW`h@VEK?}+IQx8 zZ%{38N0;7_aC=m_cp$POTVa*@sefOu=r`e12jCI5UT33@5lEDac~=g?j6s$4%g)5`L(7X{5)q2%3Fik<5xy{L3WFQ1o) zG*+@%Nmop@sGJ{0$`Z9Do)oIP%>JmAb58D53)h{hDPK9aYSU(!j#FSmBKlRuWfYj% z;iT#hlx+@c*&DdhRA6SqnP!W?9;Q6Fzv(|02O|ES+o0N_J(R=3E?hgU-;wLWY=f}Z z6fOq#KyH>=vQgnm3wue2JeU6_e+tS~(&%G_uXgO#HHWGkp{sfh9UO_tmwfs)#*iF}d1cN0D_laH7 zAop$G^T-3Vf1dAAYcC$isRfgMqCjoyWNM_0E&r zs

U?j&?4By_it(lNFv*FSy#P~|H8WQxCRKEpKXbCQ%?N1p$KcwUYLdc|RF@TFNk zDW2bV1EpS&OGwrnUE3(l;cBdVC8fV)3v5(Ud-!Ci*vqjwC8d!wGzH8uWqwUhJ!_O7 zW4zY%dqU)^azl+G+Fwg_pfoicHP{fwgf!>+9lmQy3c%$uXDxO4;9e=(r4yNO7F~6;#1}Uf03(SsH8B~2u35$(VuT~nc zO|7$C^O!FxOJ|{oe2F%zz;Nrsz|gyzH3;|Re4mbH?a|t_V!Ga#RJvQ#V7tZXp}KFX z+pSQ;9N(LF7lV0mIb!17Y_8o@+WfFu+*CJ2y}vInY82J$cLk?7aRk1?&i1r1!q(mS ze!{iJsKIe*J9k&9)V#PASfm;z*3^()dt!KyKkJR!-F} z7G^bVBkR`*7b1iSCY&8}065-q3iawKKN0#32&hsyrxlqFzC(N=#+1M3sHr zZE0Muw_3zeFQ`&MjbUc_1A++*Bx+UVNycJBNHjhvq|1>7Ta@e+U`v8RVcgE ze5Co2)!ufxjn<&H?broozcAHuX`*U$jqQZ2uWRLe6NpoF9Xg7vi99E)B)GNqY7G~L zLr&-V`zbXSNM}R&eZ5uQx2judwB5v$P0+=na*dIz%w#vsx0m||8S<@Td>ELuWXcXU z0{lWxfxHXIuRZ>#kqoj`eAH*~0dd0^#om6Pp@6N88q{J%s5&l z27jTz9)8yF{QbOqTxxu$L7=3M^z-4zhlHWrlWKTF5W&E>APH_$U;a2ol0mGN@5GQJ z4HUf~OL_3AOr`trY64$(K`Vf)F3fzv2Q)0o1U#C>2``h_AP3zss?MfG)!hFE9*+1w z&^-p;!itgC`}*OE@gwBEp*tIUuzdQkuzFk%XNZC*sGO9I=Ddyq`rT;2J6ZH8+eG&@ z?bms(F{8R|1YYP(qAS_fr>+-9wh>;z$;?1Iu9B(D?{nJGDU)S6m1iiNfnc10d*oW4AqHo)V4 zST}vQ!T4{c^^AKcKoR~~;(o6nPuUfLQwp5Izhs@x44ktU+0OYQ+bMwj_Iyzo>5*+@ z&ge3n(Yd6&@oe)wd7`>*yMLXwO%I1NEWs40%ptuFp}!x8^n_FT?IfRVK@TIa^vKHT zIq<)ei|nP9TLap2OV6}&=>j(=ST8Q~6u}ja!bwWzRSxIj%2waB-po)lLp`6NW)gFsjC~{0cCGl&6PC>tNGUm2{qkrwTI9et2nFN8mW= zYcf5Ty#C$uLxcB*iXE{6BCzp1fLpCh!XjYV4`#C8J zoX=7&^3X0kXv?0{>pGmkDvF{q&o|%9QZq|EucbaaSUNLbo{<4O%aoHx{%Di**D3VB zuO!_yET?MiTER~*p8lp3KYvuWcp9K-QZ7g`b+id0smdfN!-W~&9zffuA-Mhxa6}}! zZGzTAML&dRG93S-CVkRg0DlgqA<-w6q0*fg zOf%Q^>q@z=6d3hP3+``}lKc9vY4qjnn+v^CWx3s42qf+AQE7g1OQBav^l97w$%ITu z_{%qpdmA;?3rqyaC*=Q&-lpON3I|DDwzR>YwdQQv)k_QMuXB#%(_~}V#ZNRl&x}!R zJr0e)9v@dDFKn{eZ&U?1u%hRTbi0>aG}EJ;AJ~u_IMiah6E9fmaPj6`Y~^A*oSjnE zV{ug!6kN*C{v7I&q5UsM`}eYR%h8^%$$DIetzQwjCo$%I%)mSz!ni;;d7i16rsY^u zJ^Im@-5Z&V_p*}BpE7|nzGx0H0L|Bg+s0)$aEk+QBZ=!$-e@@!=fFlS}WtB{0FIHop791_8I0r=cP;dmT ziXC z?HvYzVHh^s>g8?Zu{c936d}L-jg(B9SrGL{gQyFivT^PxE1k}oBKS$n%4RscRlE84 zWPR@1jBlW(E}%-Yo>>#p_hC5n+q+tf6By)a|>&iGwsR53M?1I z(Ydh|#g+xfnCviTz#UcKND>@zLSNE%MN!BN?gnkxj&*f{5fF4ikR|bGyq};ril7My z>_1OqsXwLS$qc; z$`N4Iat!braMO!sDf1l1GSDlvke?|Bp$~|>sRix`KIK9$w*&H-=^HEgq5k}dUx}E1 zb^-S9fgbGsujm-c-hN%bP8&JnXo5K30R&5~pr7QX zmTLv@8CS=d;nS*tt2`|zg`-u!X&M(KR))FLJ=OT;Vl)r0T&ik7CZlzh^_&4$S~dXY zxTXU*8+mA1fM<{2B|7sz|NVdA(ofH2)gJ-)i73bI993S%!zzWgvE@ldO0<)v(&^t( z_m_1wy?Sxa?~wGl<22vt@yFD|uai}aeu}H-;b%^V`7|urL(Y~UWB?_E3amRI*$Zc0 zd_aXYP*86IeZV4}Z~?tQJhP1%`oRmS8`}}Em0!s8ME@b28PHOa4yN{^->HivHgb+P zg(N}|=|;2$Hn4*+7&tvmf^uM;KqqdEQx6wO769!I@+7sr+vb5)!LT1*TI%jD3GEJ7 z@0lbF0MeeE=B8+dVn~`hLQz&^NmW%JPHBIuDEbpoe=4SzME$^qVU_$~Bu6d4pC0F2 z-&4(;3KZvVPvA6RfU1F903a~7;U66&o#{wgZtX@k1nfkYq8a$PiS7;0H~fA~&aYTE z_feZK92!22)gNcACFwd)8@xfIKd0t(K4YVp)x8d79N`4 zvoh~{3+jG{7*r{NHV~}Sa3M{u&`G2+uv~g@{7IEThfri27IYpUq;o)&2jDv?sfL!} zx#nFd72zU6GBh_IMR{pW6@k~?N_=ArUW8v0ghVN=4O=pF=pmp>hI~ahIC0h?a1PTc z%z*8U1>toaA@zp+OL~#>pf?X-_6t=$`4M=?{npWSYcjD78}#NAY6U%IY3Z={VNzd5 zRKnjqjDu4AHNB*82~PpTLdne7B*;6#^4Ql8|L%AO z#j&)&Q5^MSPksY|vI7~J0yr9ww4hRo@S-0b2HQQZ1$Kzo4X!sHR zImaFcIzR#hmoV4>2nmhv^B6{aj=caOuMaaWFG!Lt5Y^fKI<q~hlWR?;|aYWf!sSeqD07xQS5cl59Dj~Fa#U|iULxg z)*7HX%t4(b34*|WK<-ovPyp-e$nkx;8=`ce2ZIWiBnhynf-Ztafnqr*sk&`x0t-10 zy9W{w;D#J)37gTO8-cC{6_-ykW`y%cN_Hedna33#e}M=AV1qsa7prU|%SEB>i-q~n zHm@2==cO5DV_meKyME~RItETG^l2Jj+*39XUxeP$ieT`P1Q?;cv>hFdS-i0C&qco< zbREV`5XMp+2Eo~_4u=s)EMT=hDMzpsTqi_1Ef55)jJ7Vn_@vLW#A&h7Q0mb@9 z%r#H-Tk~OKp1l)h zp;m6ocyZ_v< zMy#q{DLA1w+bmM^k@U2Vz&!#YX<4UGu^n6O$?gP5YXEPiUQ8fPemqt3FB(BW!zHRJ z{Io(D)wQ`gi^wd93@hS6MxOHDMH0O_@Hej+>AY+SczJ2o8Hj?77;ri$roD@>RZc}G z(|iE1n*FK%27_TFbYIZ}dXGME989ak-wjeppwm1=bJ=4LN~a<)rBrTn)7wCkbtxtZ z`{vPj8t+U$8=+mG<1TPrWT;60Ax+R-v>|&CYBI0WfKCcjpfxCC{|_O=pzOE!pxY;6 zP6&>vS*e7DUJkw@@IZUj(og?VLEkT;vug-0G8p*R0I%rbQYc6`8<7xH65_H)E4JYD z+zO9jlHkhy(3STU#3riyI%c?1XCkpK5Xs9xt1%3LT`Q&`+( ztsHHVSx3^q(xe-z-rLBFzzAE?WZw`%%1Rk=;`& zHMd#M0bVl31DsD8gkhCIX?s%$S#bQY6$hvoAUq%tMB!aT;Zq=k6PRrd3Gy3!vQ`WJ zHs9PDr;95x&4C{wL?<086JW?#n${r0kzMdrGG&6yJ}J$3`Q$K&2(S*& zPAdT7K)Jcc1RgI?53(fKo%GJ)09E^wJuELgP#=fdhQlo8D5s2LAf98me6r;IDZ|Lr z5K%%I)~QTfZGWkqpMa}i?nfIq97jO8@O!T7MJ(Yd)UV@11I}eIdv{omJ5j>DL9UmU zyi;5E6)@g2ZUNbw-iswCU+)jtM`Iv?Rso2&229>1|MoT?N5`K4Q0T2&q#lRB{Lk+! z%D8M^Mc_=sz0p1&O279DTRWqsNrA7>xv|cC;h&7ygPdQGy8-#r(wE%NF%rN#afL-U zJzlw2@ZaYdcWz(H$P+F6F5OIxF}E zBGT&4bEQ}TXHNUgzyvRVjk{Kc$O#eHW z>B^aE+Jyql59K+%cenb?=`5MA^*b|HqgJP>aR=tVSySklcBk2OYhk|=pWToa^8T*h zi;?sw`Y}h{;Upgd%s=Swn1}9a-+7^&#OaAmypAUyamwhP~nN<_K#LKpn_ke}5WaI0CZ zfUx+bF*5s#+&xI3$J*t$l+PAwk1FzrT)~RjymDLAY_@9nE2xL_hCIIjTkMs&T?0_f z!p?mSnWnn7I{lDkXPS3&F}x>z#4a59HLw|GbFxLWeEXM;?X0?X`P$}t1#%y^d88W1 zsPSqLvr2%|av^x>NA{FEcgi8{cX*q*!F5e`dV52?I#h-%Xxp0%g$kiV$LqiF-s25E7)PGnl&idWV_3Z2 z&a<-JiJPHI=(U06&_n>@E+=Ab_3tXol;at}R_R#}lgS)yl{ zzCoD3(XxMHYsC#oRcX$lfwIFS$o#w(<_u!)rw<0(7TdcUO7Nqstp`7|O3e;)`;>u!@ zm<#C|$kJkxOj^FBN5;0FWv(Kt;_zuRjN#{#Vr;bg#W3+_)D-14?OO%y8y`OMT)|F| zznAjJFT6=MhyEd`>MrlpgHzQJ<+Ba7kxWpyrzfv^-h(5I#d71@(r!+*iMZabdt@^i zlPG?4E%=9XMUmoQn+}4!A1R}I{qU(NB)A}-$d&{^!+)c#M!kLx5 z9Kryaf};)EGx5b!vp%f$!Iy_&_>cR9wB@*nZ400ij!hEY<|l*gJJ`;&67^HAB+ zhfI(abLmM^EN(+AZmHL0?xX_-{xP3)O1u3^$L|!S4lWu_=u1pNOW+p_(xLP_ z-%&;{cf{)Qaib2A!?MteA3aVg=+E zD2biIPT7;aqHOlE^!N?wF_I&qM0kcF?0qFTab<&TH{O7kLgm5qT+{;Rem#jeb0rrJ z77ImNXcmUY!rPr)C`~PSgK~Vm{LBm0B8%tP=p%mO@q)nv8F2F2lzw1z2>lz4P%Q7? z-M4`k5kP1Z93Ac>OOG;mB+ReF?5|QvFL%SHm)l&sJH;A~ z`*ZmQ%bQwkwdR~x43sOd;7|HLf6;rPUN^%jk9j5;idx;t}7#KM=e>rGu zGvpmwGFasr!>u^KLR&|;w)J2e8uZGa42{NXm#@@fx{;NF+(+TGc>ZuC?2d|(GmIX! zY1OI9_>SFys+Y@8&U?2mr3Tc>!T>ucDhj_^%7vu`bg3t!69&68f&9ysAZ>FLu8Ri3gJpVKPk$4E24-xk@b+^nY6M2fG?U2=pP8HS|Qe$Hi{@N8sIxe#*m#Ja>qmT4bblfK) zf61h0>LPgh1U8G^)X>yNRy}lb-8RNd%o@49xq-%&nKs|s2vufhpVbmc6M_P*ee4&+ ziE$m&73gog6q*m5wXK-DtSS4E*@JI}%L>t`&8V$0syL(KNm*wb5*hRuD(BW5TXu_j zCab@JCP~Vn)l$!E2$&gO^7GkqM41Wh%?A+3JNplTg_l_w+ABL%lp%{-=ZXQa8mL+^)9-_8B5IuegV0)y{2Eua}sOd zE8WR8Y`{}8;LAm3Zz5rsYR+eW=ePdGoHS(Jc7L`G#(AVeCp^(mtKuOmn(CTU$~o>nrF3&}N6;^W_~|Jw`z@UMx$PXp8!hB(%3OIxK` zZI#YlDgKUk?C4_;_+v9rYty6jPi1esy#+8R;C|*djP9ip4;7y2Xh!2!oyFq2sHcbx zh*-5+K)Y9vBb0m(=sTD+_B_AcnXkKO`l;xu?W>1a40|@QRN2|{BcRK?JSj8x`LGkX zMSV%vK!Quq9Bx`FLh(~s z2^q8&zo~n)?#TsM)H>-J1a?vyb-(g_h}Ln@Li6$Um#S2(kiRZD*BTGtlpIWctv|a@S^aav%dPb8AKb}3Cza-@i#`VSjOlqQ|Kb! zGo!)_3cLCBxDt`zZ{TPD?a?@|HPBdkn@3Zl#7SE=tlAn9WRRZ9deU9cp-gyeVnr?+^GUKuvh<=CJr9@0Xl`aiw*yiQo^-s&(21szrw z!hIrko2`2-tgYnxKN(pfSXnS{aXBbTif;eLF%=rUK;%`1gmEI*8~!hRD-aTI@jgVf z1u*$-C0UilF*Lt5?@)`y6Ef=dSl%DVyDmJ~$4G$x?2zeOvAgdMe6pty1F@EqvrQ3O zSXIMyO7eFieZ7*@)m~D8A>+t#a~9@*6wfQ?-(B^xeN2J#<3dI6Ql#xgY&p z!2F{9;vW$#)cNcSx7S)~>TJq1ZYyTGEEk`$o73>EW=?5tsHt<;QYl;&ShMJr7VH9D zW{&Ilo7V>)2PW{}B)v6c;)l+j0c#C3L6vpX1`SH*OVXe%6)r7S1P@{+XLM)@jB}<- z$SJ=BmAb36g6#FDU(U>z32NkMOD|%iN;>80TmS#+PZ1(?H79RMUi6m@*P_(|XX2va zhK^%+@#OHn86&en!MpkfhIC#94F@Z?evA42mRD_rqb?2_ho_}X=#ha2?~Y1#Y$MJy z^M+ALc<~WvI)8iEBB^5amY1{~x^}8nCnp{-#KpbF^kZqAQ9xx!zU001o>D>6D2}LQ zY{HTXisg#tt#y>nllNt*V+Y$9Vh~**ce7eGxz#!~)0dhP@;7ugUyP^9ImF=?cYSYU zHNHAbXwVr5Qtkbul&50qtyd<`EY)@x#|N}0KfEQdZky=GZvJ-f-4@|*ud1l_t$T7| zYcA1%Zfe7_ucEp+*G!MJ)!(piK@A1SHx*sS2qS}9D;@9%;^}Y@ut5MY4hci=8p|;( z4L5y(lAiGM$SSWvSTv25yFd-w*06aD>M+&*!1tk$p-tQfg z9*TE%rgO68T3kijz_Vi^|352Gi0&T0cEs@C)Nky2r_e2b^12y!Y;eb(ebM_t;ZV5n z9WXP*ads)R8lQ&}y&ahIMWqYH5>PO0f`H%guLW59UcS5ttMd+*D+<*a6Rx-#@FBJ$ zL=%Q8o#b;Kcx2Qfa1{%lc7Lhw7rPx0SOsD+x!0aQSvKL9mvqn&*CJpPbubl;^r#e= zX_cVU)HDlC(MD~$xP;?&JwF%~oX8|f(i*x9Hf(-M&jy;v=!**VaOI4HhopEB7j4_0 z*{7DOl_(*4>6Oe)A-ujbUD6G?%6Dts+6*K+8vLEdwdo#8VelenlgOyjYB)$9PTZW# zb@E|k`p~P{E;m!Z^5xdc52;`huZJ)AHvB6WviFpKwy+$yCoz~(T5QW{WW0Vs?A0XC z12}JHfD`vE>s75wyDkJsR|)Zqf^*XFX~NG~(LVd?)`Oa=#r*wh2WvhI4PQ|h1P6;E z$k=$Pj)F0C(dI2YXDlCiu>GtJ*K z58w~mP7$gabx+0nq=s8E>XH@&2d*ifsKJ8F4>gGiRUlO~bG#(UE9pZ!@OQy8lmEh; zP<-?YvUu!n_}hnZbFpUTi(|L|Us`=4bo$6t?pcKfn;{vCPK2IFCb_K|c!-YS+B+Ef zuH?V!ku3U#-tsKfvbd8Ln7H-Q-G>{T$G(Bp@f&IVYnN5EHIE4Y#|PB2gjPyYLW|{KquK+xwSt z;l<6|Q}ZBGbgop3sEo5u;e|eY8mgWj893s_JA{^KI@Oz3GH=?EORhm8s5=UI9@U!$ z;OHa8+Xy{)`7k*nr$97Ry~3D%9ye?4<cB*`az>nlpNqH;LSTl1Ra>)XlWRa67)C@H;*rV7KZp{f`BYvrsA{1B1~&?`7h8B zGrz2eFw?GZ=jlW>t&5+UWuv4>aUQ$$`D)^X79m9VS`kd%*^IwOC&SeTbHW+v%=|&A z81yP8;;^WwyIBO-*9^@NYAOpN#T}LeW_B9E`W_hu7{z=Cg*_>sbP*6*ysrnpmkjHj z?P=*fb$?&fn*Jz7`%x}D9#{Yq1sJlCR{_MMBVaSy;_JW-F2CN^79NHI1>tFB#TJrr z-ly#>X&!GQ>|Wj57DxrV`ofz9DUtJy-+N-tKB`_}4pTKD#5^nIUMJiyU4QhRKZ zK0uF%O#0LZQ<af%B?B_EfsN!@oJ=Tz z!CwM0!7fzS-%yja^WIjcKJM!FA()L7XR5_d3nO*RHTN#X`%X{%s8@gYF!qbJ0>+au zXn34ZV43GvxSB3hpG!sQgLsH>4|KnLU_pCta&lP#Zrnit( zG`9_Cf}b&0+AT%~y=gsnx&hoji|`2BnfS1MB*N^N>+2IIip{p{bF= zwC!-UoYQpu`j{fe1Wi75DLBh6D(hwgfe%R8_qE>7nxGx~&34?$Ar~nTV%H=Iowfq8 zWy6hRAri9@jM}*BcH5wTWT7#vtGWUtNvbhyOxLey4e-*f=6{XDH$qZ!GRl{+QF_#S zo_`=CHU|yuy1}3UPY69R!vV$!_?!`N(_|bOSQ>bkI`~rFhF#b{?T-j)jjU!~wWlzB znc|U+CW>m#^mym)gvpt6eIeiU#^D?qaeQdGCXRMj33_87I&ZIdYn5s}K=*Bc5Qxxx z*{|Z1jywq%e_^xZ|Ng#$0z}2V96|w#(zE5uB3+TRZO;LH4>j#aezFCK4s3g1n*(z! zl$I=Bsk^Cp)Q3`SyYezGb*N;T`E1xRyla`X3A-8;{biDjwC6A~)KZ1?3EO$7M_f`v zme{e=FcL%orY4!n=3IKGxWdwRbo4=i{9=*^SueqC|30&LOdWnxL^Py$+a*1+3$k)x z!}J}I=QfHTg81;F@YUQRCg3WLcjxZi$lUtd)x6Z)7jwB=&(3fk^%Iq>H}S2({WV|a_Y_A9Z@kAVc_fwdvi%AZ!bK$zIZH9Ffh z-Zq|7_s$#b%7|L8+9v6Y=RbemQls!1lJ^KP^fu*+qX*{$2MOq7^PTmORFSLV=@YIs z{M?YwQs1*U2GDWL=!E@?tI82anKNU7%mM_sZW@71_iMn{&3^A)AT=ogq^j69PmpbQbv$tdSl4oV@Y?=A$C>4^Cjm z*VEP|rz+J~_$L)#O>z6+Gg*dyUvD>8IHSGapEKgb`59)904NtHmsSqo1&6>}v-o&c z5I4Mh4RC5&DIO`m*joX%F5Zf*)zyDAj;V(4hXxy$Zlnr6$gh~kUx^=37ubcn`X#s4 zm+WU315tR!ciqs=xXjk)SXIeM=!q?S{Hq^gJs( z^qRNUUJl0WV)?}Tpr6(R!2tHKYhlp!pi=8B=88-m9S6qO$iJdkU6 z>4)TCBdohm{b-Q0+xJ32ePGsqVJCGFk*WNL1*gP*9eFaJN@!klHPs5y6bbQ@A>H8? zb~!n6x-$DyP7a#m&HT|#OLOxXfZ-~@e@g}1ndD_0C`*g}XYk>mrWzN%=7#%hHl%^M zIbH^t*9Kv0%NB^53(zjG^}@-Z)@}h>gm(# zUX$N~7T{o9bm-#u)!QuDK%0c(Wn_L&ST5oYoxX*+KK4P|B*n? z9~~}K3OEZoC2_>0{6m&dh907@M4Zl6%yP(MnTU95z&g+Hv9nXvMR#jfWz-|A2C6&{$h# zM2EQfsS*snQ&waGe8v9YZtsX#1&>e+h_?~>`S}?f$ts*08rJUdXIew{NYBbPOwzB; z@P>eRye3M3eUGn`a@VlySBR{5*8#HS4rp>DL6fo%*|$ixwSCwf?f@S*o(F7woH5z0 z@FAgKz4601LFW0vs|9bPP1$ZOfvn%CwDkuBP`cpszsb)Ga5CNolAHY-0Wawx#>9RO z_ih(p@Z?>_y>|~L3zzt?u)LoWitd5i&7A_c?&Y)NtFVB$Lxa5P~hzXdPHpJu};2EpQ`g`?+pkpl!|s1#CHT6Hlg9{O7FP-g;pp>qu&5$?_6*_%L78n<*stN+fX4Y@V~BL>jNa*}~W zQxGc4V!Xp1v$tv7n$9p2x~wBhJdhMUNMuhC^^vJ`z2|aas`wP?!YvFwZduPqV0D9a-zf#R|6+R}xqTEq8+JVKIrbpk)AAS^-P{-Y#>5%WbRhui)9de#J-eR7wa;VHyV(3hJn zU?J`uu3YQL&OG`M9Lj3Ur95DSi1o+0zpW|pA?_uK4Sl9>KSKwE&Oy&tLVR;gQBPMDr#E}0rci~%aXh~NzFiRkd;Ju_-2v&L$d6|W=Hn>2x{L_V^Px-HSfYU(< zxH0DR4ZL$e1aPosMNaW`aO>%KK7lz318c9}5V*d>=C}e^M(f3}B1IgKcW-0ngb|5U zu-7rP<`ERLip%SN>k&OPI~d54AeP$&Kh;fcz0WqiL)^Rdd!odD27HHCJO;eICIH#w zV^T!T?I>Ez*n_SW0AqbEYI5fql;Ta`hhoJM+1tJxh+^?Re)jv#eDdY;?+R$5@_=n) z8LKw7kZBDu2 zeCYKoqIkyz?=0~~>N7?J@m3%l0<_Z(Rn#F+cwhB*in}gFYuvgsAE4a5szc}eku9XT z%qtJ~Z162|BSHRqBbhtC=v0B433rOr-xKB`#2q zmDdSN)t%;n;M@=(1D}tFrpldcYsfjYVXM?AGQ9j<>~fiF_ey)K;wlyT?=@f8V8q)V zA*m%P5!(Myb?ftYU=HJGD{?f^({5rRfHC_JTJrL1a)4#dvhPHNl9ne$=RM~FrH2Du znlRpC z2ALCDTRV?CPAyo2l1k&%NJ=ov{GL$`U+CO?`anfr5#$0NnPsB$_cyaN4oSTM#tKPv z%cqLnwwbGoUF}{Fm*nmj5jy=xz55G#8EXWN&Hp?$u-WBq9sc7W$XNlP$Wn{@q3**5=a*)%AvFj!e_Q z#IAGsz#cwniCjsgF|2Vyx;b_3ZYk&T9;a2dpioz+}G|Zxipr4~)@f1%Y@C7}YJwf(r=*ugML$=Fe?|WWEGZ7t}ZCV0vo= ze6|%U5E6+Q@qiV(|FxtH(PkBARQPD@6SMC`%S=OkH-Q@F2?@{w4@NS>vylLZDz*@! zgTGDInE*7yS#;^`V#0$QGz@ve8bI@z)uhn!jnoJyzH5a;<*-^(jDY1tq4;Tf-p}sG zC912Bleh&!t`+GJ#k;x>@YW13Q{T9L98JQB;BSXghypX+EB@N^Ndf79 z^aSol6y-Q<2^<=#+o-Jo0Y#`I?nNYlq{QA2Cdct_hff;(^^Cf!4rZEgAj(e)v(E*f zD9MauE!aH~Zi8qbnN|j{lBFj(3UZE|XZ?XxgwEQeJ(~~R>hv|JO|)0~Oe9#QkxV4U zw+1KTC$srev`x3U7-l1Ec__zW%8C&5C()?xE4?>q5%N$!s4h&xePab;Zd+cN1~5T4 z<`e1%H2Zcj^Yi8Y*$2wvR~S}J942iNtFV-et+f~@?CKDfTh4y%)QC4AF8k&FJ*0KE zYT#bTLcQj*1KTC^s?`)zZ~-nk1Cb{*l>f5lcXR8KcQE&yaJ!cqWPC86t+RXl`+IxA zc%qxu^)Cq-TPfK zHXkv<*LI}&#AQ!p#+FgQ9pfOEsxA)Cs6_XYWHFSD44N|V9xhxR6c<$jS}GY&5l^$g zNE8cGu)p`u#5X(^+~dgs@M`yYkS*CUPDaZGO+M_=+yv}H(gKLI3EF#(pS78 z5q07$kI+;jww|HK8yC{pIgLx#_@OVm*DI_RVGr);iyH^$-o!{sxHT^}+d;$=D9!$Q zJpONx130H60;3*iOPh;dp3AeY4rl$4i^hd)Ia zz=y#1U>L3|oY$5>g1q9&SP6rQFd)`Y;DJ0ceM(T;M-R}CMqJVuTzgd2!%)q_6YqQX z>zoWdlHhPx++!<3Se(tb1MuY>+NJzps7%-O_J-j|)Kcxm!wmnV!MA#-D_wyTe`ZMR zy=i#b>tLe<06|nm%!;WkfN%1Q&08fZ}=7n%dE~K!tF<<1E(uw&du zfkNU7G`Pm4q({f5%=YX^F5pcmhY>GuPH%8Y1ZEfz=QIQ5!Ejr-O}CdGikrv%l(EI2+jRbPUNf<>q*iE`>e7kwljK<D)VIJ;4E52je8 zj6~~@>c;Lo2BJ+p(w)WZ?K`ja+ULg5f(|N5)tWJR#R+pAktf`*`AnoN{(qyXT+j?6avax9QvLe)N2 zg@3Thxsg;hA8bZ@%)pRzoJZMB~SeGQ9O62_{}f;Eicv6b3P0p5J-El;ltOkin_=M(*NnR=&m; z9cg={kB6dS4gBJ8W(^e&2=n?uEsazxTXGsxk$2I>Fz?>Ijhb>PQhn!P=KZZ1jub0G161~V(VLk5v zXW6!hBvUGBNd>qKF~%3wu1m>KUN5MLFJ?Oe-yf%)j+JDHi2Bi1J9hRpQ^NR}gl8fO z(~>=Rq(ua|Jp~>f+3+yz<)6TBNwmx{&VtCZd-Ak%% zmVZVZ#xWasuq%~46ur1J&=t_=ml(#00D%BMB}5e&I$pGDQ>}i9RUR660pTXiK=a>| zeJv%Tfm}+5l6>Q}ITeJ#+!I;FyMPr|s@T3qmg6BJvQ}H$fC=Dx|EcOLu2m5b^ zXk7jQjDl6+OFMK=nJ>pmGGcV^a$kj=x);TV>x)C47U3;corb1&+UpfuyYQdQFHeO~ zU&v_Bxqef-ys^jXeh~LQ@-_uPrGV}3PS(VW$O5I&9H(4#m<(_(RPhdnm^}Aj)RBwW zk-dXe;U?-)F4c=@doTCVw-#cKN#Wkj!fkSHfd{%}5Gs70`Ku_P*Aae46TE<(+r*TW zrb?MDXY!Jhqv__DVoIetpCFGb$)0s?p-xocAX%fZ>_%u`z`gn<+wR+%LPLt-<^T4nu*&**tjvgV{LPH|#Erg#vYOu`uZ7CraN5@}`ga(!CpE(i-^OPOozhtS z79ybnizFN+EY3)tGF~oLvBu2@vzE9LnKzThz!cc;E)w^<3*l%oI*&H-{TyMqgV*FG zx2Yk!u?HSG!hLW;g*s@zxWwD1zY_y8Vr|!2gtzi!Uk@*u0=F$vw@LIVlk>b&em@2| z-S=^W@055_VUJ`pdxFVwHv%QNBFpuZ^VKPtE$1F|}nXGa}r4zurK7BRp(6 zdr?RUt2TEanOpXHX55eYpOd3rgn}04N7-o2#AuwF{|W~xpnAV+-=1A42=<^o6V>r< zcf944m(AD^>d_arzYdZz?6|8ko`g_4rY;Ip_;oK}?NGvz3FI~=hb*{cF0Yc!r>z!f zScZMO1fsFxkW$llHILbn3`L;zLQwOI1#0CYyUWYMAfrP2nxJ?`K049>?(lq6(c~sb z>Zm{ERrRGk4}>m9`~QG+YyAFlz?9h?|28#rA8ByUVt%YR$*8K#yXVV4*P=;(Zn8)5 zoFnQSJcv?kQx@`r$FsY8f^v@Cp+c_$p|6gBnq5=G#f$t2yOatGc_;YI`dW z6`g_O?X??Y`G(s7;2O-?$bQT|n&NgIH`=@|xa0E0uQaY;VXMII*f(bpv}5|(^I=<5 z26S&N#456X2XZ3H+Hqpb5?ULz{owDQ+bKj~w{HDq>E7Bvv2)6ZLGMclHw?afn`kHFRBW*rePWv9cMZ5vo^QU)od+D8aaquZyT1~a8C^UqAa!P} zfGF|AYhcFqHwbaObcQu^f$#2KL>H0S-p-70^ERktBH@6tiFl*Ee{?QUne{p?2~KAN@1YI#rh6th5S zs%99GhtKup&>$hD=3y<$jnk<*s+ohw++W%!shfi1OH%{@{p7%F^W*X;yqnjdjK!w9 zkqOtKTo;ERxP`x2J^j(0CE>28^`#A}9U3|HQDUvK^cD`Q2kvXt1$m{Pa=%7tWRu31 zSzN|#QlDs_#czuP)O0=wY)~0mO)@9sr>zQwaQE9;;^s})>7LSs)8Z~I?UH5UMA9y1 z9BZ17nijsj3$`W9HvjaL>axKmMTx=dEwtfHBEH_e7uGYd)TqT(&UDh|Ui{VQS4*vB zv1(^;<1d?!9!8AT*1TLgPg}lpEOeD<>(}m4ZcCjcXYFc_r=V5GpFD%HVx#&|hM`GD zh3}a6&UDqX`<*Qhdk|QE3+TCtXz#Grr}p{X#!|a51(#;q+15WHUZ%XSbFt<^$#@6| zaaQ8R{5Z0{q3JCa7g{xY9Eh=WGv8<%%aJ*X*eZW4H%*<2%UI{~vM~-iic#c%#SROX zThnxvE^^SyNC)qglkhDmQ=cqBZ{gjeutBq`P3+NLrcKS%-`JCsP4lT;=*~>@wxCMi zrqSf~cAcA8t=fa*bT{$B^TLPTs!)8vb?y*tty|rkR9kDd{O!g&)8e6ni%5Bb2{3)= z;BxUQG3gwh8ki5!#xBDSgVy;g-I@VQB`i|udgYc3-D%y(m(VocPa`mv7IlJw+pzFy z>BGN&o@-=w!8svgqFp1d6x;c@qHS)~NZpiR{dQvO+)((J+1CD?R^ueiM5*F^O0BGA zbPki2Pf(9qRzh5b)^kO*f=F$pBv$|F+|YwMr8Kju<=DhdYG&jibAhmGJox(DSTmfRh{d2FFCs3jTe=;MhFFn61>DBca3XfhyrhO{%-y`J!d87_*I#TO4Pe~qaol%2=8Zjhc_NQ2=4a2vu86q1-D54j!&DqoQtGaB8{Tb3|6 zv;i&?NoVGk_GA+ zRc;B6F>O)FWYtjMGg#B^^mJ95K(q_#L{?7K6R5)RyR|o~v5!Wo-IX33X~_1_(ouR) z<3*OZ61iV@1>@@KH6{#jX8(R4K98l%OgakkAa&A8&>*Fe&=-rkFz8gvGBp*c)hkuI zvg|=hG5N=6b@s#>b!5*;#gwjTpgL>oucjjn0kYI27{k0z?4j1a6{|QqwVT}?Z@Wo7 zUg3_oBCfGs&!`y)-qxMTp`wypR@OI0o1mtve5&M~mOBxvap8v5GJHDqEU>~fEt#CM zWW$Rp5u8$8iOR^(DZ}Od{U^}X_SZaPk#${5vaRllmD#zlijGEIB<<7@CVJ;Y+C)?$ z`)|~vU5OMcyAbmej5LE~2^zrh?g)26l0z?<~njHf1yJR06( zlW>QhTupQg7-vo%*0q5U851Z=`tUq8GUK%jB}-3To0ua)2}(s5nU*G7V!yVW$6-C< zUOTj_$LO`L$7zssmX2m=9S*o9*f^9INZTnUe(^ewtGr;U{0K08$1PN4z z*U|=kAJUxG4MP3M>}>3AaU9hP$!|p}G%?kk6lZmMVk2TJ1~gJ-Y@tUDHGp?lZ(FKD zH@=LL6xP*?~9x zGJK4u#0*)!0fRB}?f@RZD*fhf1$|5&1scutDUpe;NMx2~D zDleFx5@*`)&YY5Y6ZX4y9XftA&AEW{s@Z&N^v5;MIZmZ~si(hkV1{c0bl# z=QdHb)|CnEE-uYoKGjdHn`rBQTF0JVC!bh^%@BJD%#_D!VM(L-TgaU~C4J%WJM!?M zVm0~KzWG@A#c;<>_zG=hm#LP1eaxTP1_s8r`WfKdCkJpETyzM@{Ce z1zClL+I-Qm&OYCzUvOwGTM(9){B4O3HEaJ4a~_3`YG{0!Aai2P*T~d3&B4_r50{K% zE_-Tfrfpi1I=QgpG7ixGBpyxcw2{6%#d5L|V;;7)R70+?*PHxb9nToc8g%m#PZ5a) zwj8_KlnBYGdjnC{;UyCAMi!7v(DEa+;wj$I@r}rNwHHB@;}EUD7Yg zSFY;URKVGYF=siyGt&X>)Z@B!6&@t`M zvQ>)Amd|LC_?Wm$`f9l)j&F&d0487PD4CQY6Q<})E;pIk?kEu%9u1VUV5=rl6yintU=u7;1757B6!?@jqwuFQQ16nHwuFbW8}({?ec?o;UH@ zLCJt`IPSViU|6cafp@J~mcwAxk+2KJ0d_yFw0k_4l~s;wPZ-1%eJMvZ{ZrHW?Q%jS zA2=(FubhbqYh`Ae3hX^`|+!7oAz7-A)nq)ST@WyV(qglOUFLpnA`JJdqdC-{whnMtm zMU+#!e zk!ILhUahK#(VmsP+tp(o1!LOxptMaUb?e&dAypVkWq>LUbGDPLecbE76xitHj;kN9 zQt4z331erTX$`5zKX)(*oaA;4gT@OVa3-9{p=uad1letV-TGXT5Owh~QBTi?$rzpc zUgYd@J{CPouSt_)3Bk8lA18hztU)W~eZoK31_neyh<)EaE&y8C8Z>c$bxk%Lf47p} zrneAQh9E}f=1Tmm`Ygcpm|%Q*0U|gd(1JiW&^smVptcT&^2D)r+5;v(HIPR$5N{y1 zJznwqs)U7#{!abHK4|Sz_y6QW19r;(Gven*24nR$mw7XAnk-UhY{&Z6DTe*_4^Idp zx5EJ$92bYvA30EDp16)^VmeVCt1f)6BsV2~8;SFr01iJgD>r!uI`MsnfQ_hbjbAjc z-LG3yNO$25`d}T0Rv!~gxIPr}SSb2ekKtRyj4~TuK+)5=vIGoW20MfUFqz28*c-Q+ zCkrtoBLPq%C=*%_jbQ60XN>^=89$%?%Yz#il{i7Fz=ghy;C(NGpKf%<>Jcj1 zpxr^Dg!qCYrQrVoR5=Ack?!o?@FBJ=zCA-fd4r?$BFD{0{md!w+wr}>RwjElOPe|c z=ck2kjLcI|9<9HwwtNv>+Mx&>Vw#Y=**V>ez~fH?{v$L!BK}Ju-t0#M!A7$irmZ)@ zf9l_iN6iI*0l>HGH`#=o!|BKXbvWmMv zaahDrgRK`0uNa=!92po>uUOrvY)jsEF=x@mnQ^lLyJ zps+hFh!+b%Cv~rIQ`Y+c=m1E+p;7*cO{CNI$_;@Pt2ieaWuWH!X7$81DiOdFZNW|I zWdW!Rk}wh++5mDLHX4@{MMK3n%7vFx_&{7z-O5~LI1~ILwv^y zG0>q-$RW=-Elk~65_3%L69-6$058IJfEogTQwD&*ZMUpV`sr5(Ch-UMT|3~rJ2`*A zR6=0u20qtvDyDxwkGmgBp3?muRzn0XxlG5qH96o;OlUD80Fdkm-y3;25ki`r&H&Y> zQ-T8<%3gt3C+PJ^*y%wzfi14d`+H#ccOno=(Fit{9Cl&wos%2*>jVqTY(0nYTse6# zQ*BzY26#X49%6ovT+`pcSKgG1);}8Do3{SgZ=>Il2nE{a1g|e z9&Yq;PzS4p0&aImZs=9_lWXlf=QW>!|4?qT>91{R#+~$BR-k!bZ z{z2#F(Clj<1yF!Kzq0b2JZiOI0#CI`K>McdkTc(s*brkt=k{~b4on@;RZ!pQ=+4Vq zkLQd`+gm+>w|20r_3OmOOIxG;;@gzg9LckZ2DtoqrhZx7^pIy<*Vv|&8{w_WPvRN!&d&?eFTC|9l0 zuSN%>T-AAO(Fcxl?uqfb|2ZvOt+seW?R`Hvjz_Kb#d8eBWk&ZoTkh5d@S>7*Tkbw< z6=xEGXdkiym_HrOUmh2Xm=yk~y_Av$8<-n#e%H8sOkXD9ITQW#H}%|Ik7ovNt(j&& z93ma@>@GrD=*<1m@iEIX4_86a*f6ZYVVUWjV8df}dVLGIVe_8fTqPf!IlC+V+Tu7C zC8$%S5|_Ex(m^QBr6G>+hJWF{_1oj_-5)Vj>5aOJ9UOp9dHHJkIUUDb7Ns7>+kY3^ zrT(Ull*2d`A8!u{s=q=-~W2j@k@pD7759t57@=Mn|P395bWH72P?hT9&`+=)(L&o%E*hdkgR_+emwA*rv(E^K;VL>WEZo0eEc4fh;K->YVW z1geCpw?}BR5DpEiD}eBZ%yfGS*-LM#55?P(l`z+Xf*JWT>hF;w6vR7>ihu*E^su>QL+&- z9zK-YM1%d;f?Vkv(jT3tO?6bAYxy6P*l%3mt;Q}pG^Ny~#i)u`%-du1hy{!j7;vIo zfjTyPcX{9YG+NxpZ=Q;~v|D;H#1*t5!X8olvBena#)9Z#{789OMuBZYK4andRM79z z%B@3yKj3AyfagAd6u8WsBE8NJg(d*YW70UcZR@SPa$N1JYTJG#jCH^2%B z6NyzMWVPVuid-hKzH$ec%l$ildcibJ%xp2~5kv1<7zx6ung%3?#TLNM5I7)Ir>X*Z z32C*wxuIP(O^1l0T4#UlMZfvFDgUYg;fF`c6q?9Y7NQP0KvXIjY5=1!Cl%?O9%+y7 zNS7~(+M5Ifd$dO=%l>n@Ya@GvU@zK83rsH5OLQO5?2kvvjunM@31%T(3%0t*rplS9 zhz8_FJZNSQdCiXcYZTkYAJ@>X+TiPOME0`)QRbaS%l&ZtB!7qKtxbtb3{_XovLbJ| zKOM1DPOM^Th2fUv_@C{)u{9k|z-5hMb}m*`em;TLyB$g-`Ii2NdWx)EvrJVOK(Us9 zN)Lsg-^5L!16xgic6rg{@%Qhy+Zm2B{t|@fV0l60OldD#5>-P z&gRllZ~Mv!cs0)dFvaMTAma=UY;e%YBbokzPe%d)dvfAd>sP!zS5*>Q@a!f%8nzP! z>Xw(!9aeA7MEA>RPX_klxOIuHq?pV-^!3g=&uhYdGIt_LDKu``Fn0!MOrJjceywW! zC=!&eSSVd2cXrJsjpFjQ5o{JAp+di_n|do_psr|@(y7a|b7+L_dnrdn=%73s)`bnw|xbk_yBQgv)sR`#KwN}q0yWQT&P+zJ|~=sWZ-xk?|tXh2HhUdmIinl(}O z?%9}F9zW31hQq9u@71eXxoJAgksTjkpdh*Ed#)6T#kM+=mr`99>A@T_S)F1gUYU$` zNUV^Y)>MbDm2^^&eYbJ9%7xc<=E77KGA;4+*woTV)E~1bVOU39xe}vjvPwLOST>?d z_nfHcbGJ99Nx!jcxmn(R2VfRk1a3vfda{z!mcvbEQ4X0(hs{;524i-R4mV6ZHtK|w z5#C}-J#+66ypYZp2FuQHnU;E&!(^Yf##XQ)InI%)@9^D0ft^5@eVi1YnORp(q=9b> z-lQW9-lBq4v(z~|$w~{aI+kz`pDpWGP^`veB-Eaq82$2BE1?NHtMuP_A{e+HOgKMpEgpsHj21JlAH7 z!E7cg`mB_IL#=)P7b_B4HV19APed!#pV}X*s->K{p)PYI4itR-oG>(&%fez6FiK5zh%G?%USU3$Hx0X&wtZ zZn^aLp?eFNR>5M*Rr2SWs1iDgD367Ld2vng?L8<}OI3NK*ywhF7%LBPS7GT&X}~=f zgiOcQnHtG}#QqX7^6p~+w5>ntgx-eK&fwgY%<=-B^sS>&SwV>`g_oB_46D%f>?WK& zo3f&(?Oo5mQ}kfB>PMIFA++?;ZL7hk?e?Zb8j_hOEQ3FK`zU2aiWA41;?gS;Ck^Qm z$i1368+`zUVBmilmmW2cVsI$%YSF@#GS$5!aaFD|l;EvMsZ~^9nOQBCEF$xBVK!2l zso+ek^m&q+H@M1_J0`q1C~ShFLMsnSFtnRjkl!z|s#d&r3p>M8b%iR3p|&N8QCfx+ z9y6$-GdM^g5X+|>TO^uW8Cu1bB1~SjYU<43)Hb#9!7MXc=}) zjco~(W&?B9^@gyUuNoEF(eF>GwGp8L;ST92+qFz#>p7<8^Dnd$owB6l165btoww4G zW+sn3_`x)rS-4f5d2lXMQVWaa;U1deC&w$j8oL*gBouEZ9{5!71OG_N3{r4vHpua% z`zQCqZb0RDe|Xa8%s>8VSC=>zwLxWm%c}LhQxvk};{7ca4sC=%Kk+_m1zMDl&%P&y z!Z!GId`&@BOzM|*S^X(Gm+anRFF(D1-Am0qZ;eJJ8Uc|HxE2^~2J^Zei1FKntuuCp zc9pCI3?JM*XN1|v<=jW2vUde2!7J-3@q>49_ePizbMuZ;IfbB@HL62i;%WG z)I%@$X?H?ib>e2-)+&&mh4s$T0TIg|G;eZCAAfj}~s|Uc(#+DG8SY z?miBZ)jEI=fM)a!J%BGs;`+ZvhCc*&HIOkLBrTByo+=l zMQk3T?33bEPNM9h;hu^=rw`Clr9DRDKI zRy0M{HJk~RuT|>Gz!Z>7@R(@s1eL;_vS^!jrX34?yC4Zua%aO|!UE~l} z+L)4{;|+w(dP^51p4UyPF?nwIgyc*V+{S4PK)0&T;{97Rryew1XxZ4=HpyeGlj*^k zG1EGw?QKxsK79)pX{IZI^L*y9iw8#9D-}^E(HQ}NzRa+Yqn&M^&>j|HS$1_k{ zud9#42VvDW8KkBH58)M&UMq&-6ltoUyf!JMFFw%3FPxI80D^*(| zM5Fm|U#1z8;&4O~@}|xZ|JLh|4bS&^IaGs+!`k5(&+~-~mUw`NW)0vww^abWDFAD5dMB@Pwq|eV7S+WA2i27`TTinh2^k5Qp^JM zGV=(3T2W21R$qagXWk)Msn{(eZSMGYe+^RJHiOaS7@wbf3!y-E|Y_ zuiuoJM3x2VpPt6wA!Qw6`k}?%JkHeIYWI=fL>@4x zupt*{FXEAx4DXEIK(ELTzBg%gz+SdhE;;PHy=gltv9_PHph=(MFsJc|UkK$Fc3(BH zl?s{pg@o!dE?G8Q5K+z^kEr$=_80|v#rcyPj_M+H9A zuDa3GWw<(AvX}WUab~b|Q(12qFH;uCauSu$V-C6yMYltdNO5GBqAR7pKHV;Z3r%b)V^bH*9W?)~qmY{$!VJJ&POE-P+Pwcr(Df+3BpLnoZweczhG+ zIG=FFaa^U4O>bwQ1TSuDhx_?afj<6Wk*&RO@@_sHG0{DU>i1S!Ki?2S?g#QVDzD$6 zk@Q(s`>y^+C<>v$1$KI$7)Kr+{?|TuKNM7>j%_OBBKL@}Vh63$xx3(pt<$@u9}v4N zzMPW*G6%XxkR*;<5?DX!%r;@slGyS6CVhGY_^X^hw<-cbDqbl1Y)!-VHiWDgB_gL0_gZEStqa{y?u z!aZe2>xB1eM7WV(dIh`IR7htW4m+cqnD5u{D8{$UK*VvFwDAm1MQi+q;NE_4AGO?a zQ{nh?N)K&v7$Wz81-~o^nyHemB7)RP4+IZA;8lROxV%7Mp9!z=s02R~;8Sv`1fyo&g@K*`+$n&7Ho7k(s2)v&(QWg=0XsC3?6TOh zpVRwN;eVnC^%hc30B8Sr4%cemP`VTqW@W;4ZU|5HdE6ZS2ob2PDLV}G9z!Av{R<7a zUtW`B=bsi8BxdM(j)@x|GBgId0U8E;nr(HrE0!?l0BFy5x#c8)4uTKDRDT!s<}%%~ z3o`)a%?lGo=U_nSJhX}3J27JZkMGq@pN)|3 zcJK8YVDdNX@58ebPh)$<%h^u!dtt~(h$;hL+b}#L4Kz9rt<>U`s_iumskYl+_hbDn zCcA`zc&EXOJL0$hI?{XF`^lobhsvuqQxJ`cGT;DZ0ev#D#*`Of^Xvr&(1-J7iz@9# zGk0x;POX3cuxKH}rQ7hc=Ev&yMC0?A2}8If_5MH;XbH$~L9BqwIS|hB;{XwWz!UFb zE#T-fz8iz<#meuF$Yczp=nj)9KxXH$}7ZL zb2r6t&(Gw)_-mYkh*5IcejZ|nm@QLxmygTADghF`JP~#sWCu(nTNa!R*|q^5O&W9f zA7b%yAtg8SgkeU4g(JdbNGe3$Usw6Lb|7i`@vB z4)zv)1#MNQb-~Ap+Q!z)+YBiPmTbmz zf1>dQ2V05cuTvp4I2lMDA_zx(a7Lq{At(e`=6o?$Ut40`Hqmun8H&xZZ7W5qW*op= zFT`A0FdmuQ+i zrWiQ)jE@BaU#f#s0kB^Yh#rq9b!4?sJVPY4UCTh)kB0;eUkIYFed?e8|Aj+-06eEy zHUPM|6v$=~MtSn6XEe#f_Pf46nzk z=ir13sFYfkfV##lw~4ScXw<(`gVA&|AJ@oDgCXx?QkB2Z%Ve|BK;*r7=XX+JKCIc zLZ_;JYu@?gI|_tHx7v1l*Kh4w9mmIOoU`%1m#v+vPJVRWRX^@dZwHreSa61peUhnw zIN6R|SZG5)$8N8WgL`LY`1rguG) z80gR4oiup;Kg6i{ml)YDYl>ENjIcrQmzfiz(W&hM~FSYYmr^Vyv*rE%ux`zMJ zCU1O~4WQW1{i%>d*fHL;6|^2Wuf{kmdkEn-%kWP%{+q@Rg49!|WVHPx8mlbcHWKdG$IXS^=s{8r)sM@P!WUvr>{B=LM@as|g=|a=2Ba=L4)iTEiNmAutbE0H zYox~!$TK>Z`}q*>*T6lQZsCuC?DN2Ym@UcWy@pt5N$st$t8-yzj^lpO4u9c$;gAoJ z#K)&kIDIWeQ^D8QgDM;SkJ`Kdd5(<1mz2ZTzELEes0)qjogm=tK(wvL+aRQQuM7Sj zF#p|`1O8{4LrngA*MOno4=zMs$(JqbF)71LyXbr1BF1qPoUhhVH^DmZgq%#xQl@a8t!VKqRQ382rrBxQq z6_Q@mRS_kTO;LP1r~3m1;lV?nJ|)92m z1yg;|DRal}`af0IxoCbdHdh59;u^e#B@P-#EOHhv#h3dM16T6>K`*~3 zhu_6&dnw`m7;o3pe%{!=|9&^gyE*GRc*ZsO>NB@ANYo;)@1|n~Y~?wHF!{rJAWRhT zc$a{=-8o;DKTeWYK8h?Q@FpqrXS4W$4*Drix)gOOcI3f;Sbw{lg`W`QG--K6&?yN!S@56Q~==JWgA{PZ1h3?XJh8}a3dtztn-)| z6Itq&i;jXIT}t7~rSJ>@I|B*=ls6eGvjBC)EHpg9APu!txV(v;u4t~&KB;e46X=*7 z8)c%Ce!2G^>GpGgTQ+dnhZBfW>Q=IgD0WX1-aCf1Vr4w!^00aPdfLs+2znLB{-C2s zP+?ZraaBSNp&X~_aFt5pi31K{W8!#?7_X3!WQy@UuYuK628}XI7YBkJe-QRGc)eV> z+xy}kPgGY>W&U)CCSFwHSOCNOJtQ|G_j4?_L)t%}h|HULCF^Bz@Hod?i7xJOc9?(5 ztd2aC=_&7nB-Zz+EPF)V_e3z+BIEL1+N}dDS5zTR6e-E$gVcW@_Dqb!GZXK_=n}R5 zH`M;txckK;blg6v;Lgo}g+&M4s_SP1c)Ux59Ep&B%7Dm_2oFEum7ku}zN@ATAryW1 zm)?c^NAJX%D#+I03_dT=aO7SZ)3u|*X*kvK3}rZ_Tqp4 zI=jWQ0kR{JoItjKTBhc9m<$Nu{+7;$5jD&qo%|sb$KtCy!>@`|0rwTrY5VX%y=j>V z6Gykr<%FyK=K(egq%$P)^h}jb8@0wx*rfo#Mkr6U^n-F;m&F&D{q}?L_>-=;Px;pf z^a5uiXti`_`h*PbTL*oU!HZ&z6$qN^^HSZTvAih#8Y8&=c#L5qB=-WtoY+hh)#_vO zA0wYhW>L2DY#1SsO#m1`h^4-#fe>6rpPEVnO2gMr^PLdFM4v~rmeWH_WoNc>zz~9Z z>KU;Sj&*TTMfPn z$vKg>F%$+u%;GYR%-=4K-3+K_1R_;*(4HSDq<_Ys;6E)v{2#`AvIHJN>`iIdJ~wD> zh_!5+JY$`97mju#b{>g;S!@VFsZ5hPci3yMiFcAHBs5=i|DfF}IzibOyJ+hVG0Hog zXsW{zdApzr2AgkmfzN}H02!pV{>|>Vp0UMLTJ}V=!rRdS&n7uktYh%a%gZ?EP|=`R zuQw1;s=o{a-WYCqcYhK|O4O_^FW#Ihaw{8SoLPhy_$2s#p$p~kg`*7WztMt(ED%*) zPK;CcRqXOgAE_Y){iUsXLxuOL&JU}VsR^m-lh#G;+ohMv&F`MN6UBWymCD@FXTMd~ z_RU7Owr0wrOQ3+5;Qbf>*lT|_!o^mt?kT`1=yNBlQ`HE& zJc?DulJOjXJW%H|fivY*Sr)w$u~Rzeor-%8%Fk4S+Jq&qKH!eD!ULgjIj6drZL;<> z5O%^5v%H1vXpPpM(a5$;n!$@jh0f#bLA`x3SFxqy7z0mC6bBXHZHo;$?-An}apl3; z7#mxq(|}m6rO+lPH98uMFBc+-CCI=7W^FJ$9dc%0+PS?LaiMmwb?L;K&ThzFfL|v4 z@%i4*E3eS>kSXq&^?I03=&E5TvLU`9wDbtfxbaCf$S~=~s=$twdjr-oN4WS+fJMq^ zlGvX4XH_r;wBLoRb@~Q(d({-F+Ys=R7=*p->vZ5iT*#6D9Yw_xB6vCY+_B&qLz?;W z!@3sVjOlZ;y5b6+R7bninOqY_WTq=0&%u?^B2eL)c0sJdg#M`^`wW8lz7Kz4(&vd5`7`Orz-v(hcX#7L0|LTnvjwNylgL(9+z zgKb&qaMjhHc^gT0oi}UNQv9pI}5!6dNuQH^cC1 z0-t;bGeGojGrxgw-)I?wkw_Wu&A6h0Ab6G`ysW%mdvTH}mk34YN8B=fb~Utmgj!fJ zrkf{!@lBr^Yn=5#;zRzl>gsR`}P5iq6rTvT4> zA<+``> z^XR;mz%$hZ{F_tZNAx!ev`MIQWR{r{Q%X5I;z&Kq1deNasS$Rns=RB$73xcvxDtKL zb3oqqJ{RNGEszJ71k1ow)nrM%Y5EJ2CH@Nt>4l*S&p?kr+|R<}CC+hJ*^B0kwYMg& zkcF;XRt>2M2%L0egc>YNK;CKB1%rAT2rz!03s>dQX$6CMgTiB#Nccu}OQY^&mTcSV z6Ae34>MHuU)<$a0QgXv)_%)s#dC(@yM4;@wt>63bN>WikoH;q8;);S@u3$N^C4;0{ zuylNuG((1FRz=OZEg5e5p~#t+!M48vH{aIvt*v7hP%WDs9La}FxJ}lR15iPN2VJgd zQxzRIELlUG|7JBW)HHL~{m!-yS$ExoSXZ9=^gXRaGhfU_+9a{3m6;Tk*g|+M-$-jr z8cv=w7AHOLElGPz@XTD~khSTfS~Z^xjo}zJnnU&QO8wrt+p8n}ijz9Y>eg*KI%K0~ zFeuWRZ&i3@REpz~q4(sah#;kL?!`0Nq0o1eu`-AGyoR#dA?`H*E`dU=vUxmZfY?c2 zI^w^LFB53(n9lqex=()d@!bE`HazehW}ljEE^ZfypW=sY#g8eI@G?;rOFHJ{e1W|^ z&y~AA>ehn^p}=bjp0v*8qC#r0a4atM8pbcHn+P@R#3%6i8GyzDpuCeuLvG;A^S!)-eP=sBFK=Hl-TIIYx z+vmG34I&%4wTatmLL&RbjK>+>ha^g*AUEV{#A!fVFr#*S+K2IV1OogEFc!aRh*BVx z7lxFpcW;>Uv@eq37VAst2yTv)jBAXle{$r^47a_SU#+}7^FVkkDK>DBC?OfKw zwN<8aF=~k!W%H>8xgDR)q3I8*n4@9M<@U$|9#TlWBoiyUgVW%;oMo5Jt=dPLN$3FP zY5R-=W8A6$CB|mshy~B|`i_#_=7U=URpyoFuP1{FO=tUIm1Ns;r8Py!7`nb08@H3O z9*y>5r=0UV2G|lqUfN;w4d~Z++Ci^D@^#{u&*Onj-Q2CN0o9o{!)9IVP#@kcboVzX2?TFAz7#}!jziz_!=eSd zwNXBB)2ltD;16`74SyWjPEMZ&91kG{OtNPIYz!R$N1YaWokQO$WEI34;6RsCfGTMJ ziJd$V8fz{-Ma*9}+PPc8GD*K4QF`a+SVw~axmUASxYC^qu(O`x@NdmKyoww2q+?R?IRR%0k8NqTVZs}gZ za+^&w;=mFZE2bCB2)MY0G!G0Hx7M)@wGgootNM3WVL?M1i>iAM;-O3 zfDPc9>K%lnN3VwyPe|c6qkDFmsaN>~HPF@f& zJcpai9x7Jy5GFp`rE9$SD|yOzCZK_Y0*k~ZE9#Cr)vsl2d z3zx<_Ge~7;Yi!c+OPM0iShC@cwwynQGo^}gtY=}H=%Ii_n{<@ZhLXDqkigaQJQ!5K zNFl0WgWP;&q-LyO+gjY2IL7twqGwzp`zjzXnOnQ12`5b8@t>VJQr7gRtB^52>FnZ{ zwz$+`%mZ`BjR3Sg84J)o$tbVD9wuG?Au4NR2_Pk<-@>qNz;9E>XuPMp2I*{N#vd=%cl*MXX+lPD8y zxyt*{9Mj7Q@QtWs5XyZD0jdI23sBR0Gpr)4UQ~i>BJAKDF6EDsj!9d>l4;4z8Bqu< zOi$%u?I*!8hl~3};j@jm$UQ3ZR`~WGkt+SX{+lj>6Hp}^5#d$t0=;7ZcBQSfM+{uz zi@|3WxKEU_6i))oD6DaqSZ6rzU7Bjp#&bOdSJ=;F+2HnCM=Afnkljkk2}If^C6|e% zjLBv}D&C&gdlnY?E@kb|UqOw~mAH2hQbkINwFqLJL?yKrcV>PwobPqC0Zo%c8X{RkA8*;Y9c^{B-9zqR6ua#Tl-rWzWpYQ zsvFvIzH+v^8%wpnP@DWnKb8aN5p~(!!((oOSYB!bjVTJps7TU!TF!|E1~zm6?O*ui zfvjNA?`k%`F+r-n-9%leMZDM1Xp>u62q5BbDc;;4fwj7VS@EZUVXeC>FcpqaT3`@; zVgz~zKkRe<*#{sNvszpL(L=79dr|6c#DreVcK8@l8`B7P}*1q(?u`eLK zQH?Dv519W!(?QHR1Y1o_@0SGJASgyHtt}KVM;S-tRQA{3EOPMIiisZ-`;04sRoSr~}q= zuBwcA)&!J|_3>?WZ{V$2hj=&%MbZhz@+Iq6{fzUylh3JC{-O(}}4LOaR`Szp5_(sioyLqTxVc*+H!aQgI2}&?|(pNb3 zA`0ase2C_{gYl&8U`pkDaDc5Z8^6)z$;W1%Q*+Fv1NR5M$HVny8&6mt?}`7lYO}-6 zbFEs)D#H)P&R9C^#rnf6VGip+y%pYIzNV+2%O>qNgl^Zo^S<6w?6s-fJG84%d)Jws>6HLImz9@H zuahJK#JI%78gEL{+1=3nJO0h7#o~?x)pJJ8y>Fa}C!=sey8b)7UFWUPJz11A?P)i|b!G&Y5A`xAlWjOv zJJgDFjOZ}0|T@L(b3L*7XRnp)8pZC7%kP*XFs-kwKJIoz1ogT zA_!mhfcE-IYRaBB3~`&Lp(|j)Fq;fM0%hqI&&a_aeNxIS~5=8Ul8xYqD-V@-!}an}D8GIZV11|F zkqH}~uQfyLYmd8WGB8TWq)^T++P#jucIqBKRzWkQCey|cv6t2*5-Fv90yDhAQ5CU; zDGNZJ>lO|qB6rhJbqo4oLM02--uEQ}cQe3r3%b*Y8VCq^1_zshCI|>4YiqhsMYIND zVkvrgb&!yy#-p*9Nb0@cs3?sLLEPg`cM;z_40z@KA%o_-$?OYZRtyk=0!HPr1q4A`;h`K&cHBLQ=R=bT z-h(JhR2~UV(z3)d15ehRi^Q!r2WmU+(Sz*W9&Q^)!V_B9q<{PVT=wSP-6qUWG;{%O zLH0aPbM~jw{+spv?)?OIa*|`Gg=k?0C!74vGwqz7kB=;pXFhDW3XZgsYz6r6Xua8SMW~CSYzBmuYa1LT*&rn$ z%ewHvW9g}$*U0$#lnwb%?T{$Sb(65U#uZQU*hj4K3G zCL9OKhswMyrJx5VYDSKKN;?M-0bhKJ2QZ4dafgGyyF?4>O7o6h)aU1CvI=L@;L|bx zqz(nXy*c0^XsT)^4ZCm`p$a|eL7+^wbWa~N0VdHi`#8mOQwVcZ$hW{nismKt^Xn8o z&|Z@`1J!$HQiDf!gWu*n8*M}N$RpCj z%jpCb0!&@$2js{i(A}@cFYL)L;Y}UpQ^)mJ=}YP8!K%G}tNDm4tu6qxj6>eh0TQr_ z8YDYdKgFaKFca=q1GLe8NMGUgRxtZ!FDI*uYRON*=KiF?X0_f1itd|5e9syMh+03N zZ@DYLuY^`5{S!a4>gtE{j~-4cAYVM@FW)h)=Q<=;OAm=aD7+3!>wRDeDl|rr23 zA=tIAv6Vvx%&p++Ui8KuyR}Exp~?)%xNT%r)KYzP0P@k%Y(%#jNDFv@-=qr#fx>`n zm~;ob3omVMP`%g+Z$MBQch+wqRcan~)sepsT#LWKt)JG(iz-%`!cHo!z}t5dl75P)r=0IZm^#R|DksO?WM zGH7S09BgfjG2*NR#I;W;UZfQ>u*au{1B6As<>1R#(P|5T(uPjD+|f!tke?mgy;l6k zW21zBgI~zjYCA+?oFN#@HXn?-yCy|C=tm|q!bk2^p}mc_y!qe@P@c~os2ZCWxfY(` z+170Y8B~}q8NY8FFVp>J8W;*F>hU^z(y`3$z&1x72f!a!*jvCf;g5Z)UB*4Yv0^K| z>H8Rj4!-mOP4}&A;DWoLTl9xMs63V|!ZLVw4B%ZkluVM>whA&#GC(a1Z<9^+w0Ky6 z)Xa50alT@x*GXVB!@*`s6HMVemOc4-6w>ENzbdp0NF*RUt_8;JXQ#EaspZ{*_MJjQ zGL=4_uoWmdIR``vvOT$eRkcr21HT9_k#l@305Z9i*R|q22zJ=3Dn26Nf&OT|9nK*O zK|f4TfDL}7N4;w_A?A#~eXX63|8=IcEU7-qk_O^2)bF%ZKzH#L+%rpt^GhcONE?(<6RK=ulbmT+t*>w$Di zjC06xvqqtE2m(19W;rio58USEqoG4aW|#MSG<2;We+PEFy`%A(@}_idXa$BXbBZ_EpOF=Isr&OJ60Pa@#Q%|p4@@u>L z;K(9Oh=7Q5HUNpkxM&goiBvhH#N)x_LGdJPMjHP5L$$zZ!juWI^i=WVc6o6G8u3j5 zec`HrwL)5J;6*ttQRPbSZ|$g1bRb_NI-^Ayu2?E#|VCcB31tYIBa6sxfdnD=%W zO8=wVEHD}1BiB+~&ns>q-)zx~YK4FFl2Ve-a(RC302)kCXhHbNGf4XW zeFOANOW*n=7XnEXttyoLT&)Lw6j#tsmxYaN2XHutym+CGFo^i~^(=D3T);Vc-}2%) z|3j_5<>mV~ijslvxqo@*`a$#(0c(3-a+&{*17*A6B^(+qV#?zo;?KH>B&}ed?SXLl zywsO{@e3pSkH#XdcwUOWOL;UEz?Es=GB5Rw>(n0c{?!`$!aB9@YAgf|1p@sVdi=9> zYCodk|86ON83=we4F3?!_(Q!9#FDG8{2)$TGroZ^`vUFw(?!_tl_QrdV6HeD_(I?{!S% z3tu9A5KX`7hqm93#PVymYt;A8HYNYT&HBP-)8E~!=X*-OHG6%ncB@Cl_3Rrv@!qsH z|0uD45F`EhvW_v1$~1*D#HXrb{4PWbrFcCZmHHw-2UY%Z=Z4tZoM9 zeG}lx^HH3fZ)a|@{_f)uDjfQknpNS@e{Z|P^Tr1i3jOiTdNmY!(ZW2r*qEjIdhVSF zw!)xa^T<~MQeXdQ_-cx$n&NqDQnd3Ax_;zqXS-K3bsqqcg*U}^X>rRxcOf62X8&!T z{DW}hpI-75rC!fe-TgLn#he}7o$ER^V3qv#(}e@(3`>v#Cj8~|MXJuqkCiW!;&pXa z-u|HZYq_(s$giA){$8Eb!%67hbtw3|9UyzyGWF}{KYu9O{_gXi->b&k`OUr1Rk#!P z>N)}HWye6X9B`{R#19oNc}KY9WoeR%MB^AKz2AOM=vg?JwPkfo1cS6~S*x6#%SDzjn2|6J0hMNK*o<;MHhg<=;L40DlMD zpuB~#qAMOwyxOJ0Fh1&@XIBUBekl|Z7>81%Sgp^gLg5<^h5x!hn^6wKcoeHTu9Y^w zVQs!hlSzV>SP82%xzgmfsmYgUG0EU8%@frDaFrInK`p*SgBg-hM1jC74X!l!ZE5gD zC_*7643&we+DlVBfpdcR>e_p5#3ObT4Em_D`r{>$z?9s`L2VM-FP?>Qkpf-;S4LnR z2T?5wAXh)t;?OJ(8tCeAG5|s>65)|oeelQeg2V5hg7ej<+B%3%FQvo$zkh;H0WTsw z{QXnBPxZ+DRP$Os;0%Y6(9)CQhNKuA+(eI`7>}R$=}rtFD~j3`moR_g-Ca6Su$u*u z#lup;z3!W!?B72ncb8&P`WYvPOq=J6Yctq|deZf^(1fJ__}}_%2--c)XL=OY;3Xgc z^tBggYdy4!40_g$6Ug$%dEo;;;DM)d+~VN_SI;+xxDZ2V-hzL{+bh77wQIti7a!jj zxo%w_I>173R-_+*vjo>W^Hy93t_jk?rJ{SDp_|)6)m;-vH3Kw)v^2i5(Pej(CP4I% zKGj@sC+dEt$2A(XJ^&^Sll~)ziufNfhN0A_nr*p>4j6A183DVKBlN-t*Gks6Bt_n^ zVe;GBhH7|=liUpnZg+9;YZ$~X+^y+XP^^0bil%$NMz*IXZf4(_Ydw0J3#x}}KF$Lh z3`GKj7`rDMP5?6$_X~m1@Q9+|cID(2;MMP+CUPtZ!jKnMe2WLa)=f;Sm8_jyb31L`2`OTy~&inDukvpw1Bkd$@BI@~p8SQyU)Pj>@b=#lAuk&TlXv&r1eit8cAf?zwI+h%uw54pP;yY&s) z$iikUE!>@|&(OsYcOqOR2ZHKH#>7B|9O)qrvQ}MeKPqcDZQ*f0p(pK?RbRT&%5l1^ zy;VF#GqNcf1f*<;>r*2q9j-C544i7J@nXIA#6yGfqVAqE+~qc(_1*oVAc*4&cO2>X zcA=)2hb2RaKoMiGBMyf}56v-lWsnqa4Z)+3+90ta;A2!7G9%O<_v9(Dp##pbho>mO z+)~Oj9wU`qeaPyV&enx2MJ&Wp4ZOXeQg>ji8^|{CNps(rAa+EAW_A$2#OH!wYbZpX zgW~BBq)9;o+6R2NY_?Bbd~!6u%L+Hx)~i!XMi0rOVj%&s}l^pmDu7!Z@eVNp<`j5 ztM$9+Tp`+xaJ^V+4KL0&bkAzHrls0QdOq)Lw)V!PwLEd%Bv^DQLSWFNAGUk@`NWry zR%bHy_paI6ES&7LtW))dG44={BogC!sIw9|;6|h2v>@0y%}irUSgMM&j-ih1^sr+D z=m}kO`Q$il9K%j`$M=ug#LD7un`g5{+Kzi8UsHH(fQ%UbC^TYWi|zSkDU>)2)_O{e z=S_r4^YAzeMkc{8GmJ%MUTfALW8IF`hT{anVnRJ*k+R7qYS*NU_M5p20GNvswtOw|r?+mb>rVJNH$4RAKZ|n0Z zw;A`{!>Qli^q5nuI$7XH1M)}?vX*Oa9K*=5S-7$BeL>I?);K%fPp1BoV38b!OznZJBgmUCMg>z%8| zkH_^&C9Ibpj!%EKF7^7!&!0aA2RvWtUs=tEp;z-BKk}!6blvD2t3RU4uF*a{4JZve zs?s`izMX~V*JsMt`b5!vD^#`9W6yi&@>kVz*c}Z_Lm8=emfxM8D9e>FQw~O-uXRS! zH+bm^G;w3594^Kku{8bh{Wh`>h2Zk|pxO++P0ZugxEHD4F2=^-#1Ded63%K5<`uPi zx98=W|1cPclh)a2aCPEGVZ#^iY6@RB8V8NDO66)$xmP-OCwHM{x~J8)KB$|bbsTm2 z=dH?t;~mvTXS#p=z-IXI=w`yH%hdvq3x_I`AOi9NdZjxM9<;_>Q2GNO;Bb9(pP9o*x9y24wJHDN3U4Y91>1lLmZef(Iu ze-u78E-&kquJss(-<^jmw`4z_wx!9**B`u~{^*V`PL6IyQ_;J+D+f-ydgRdo53ZE2 zx?8)S9Nn6yHkbyEwyx_m#DfkAzZ z(BGUeroj98pQSjaRz20e9f#Ty^GXLy+1SjFu9DZQ-S*%*XiwD$|0^sSaArd2fGInY z9Z)8#tKPSV_3@9(lkvr2bsp#Q=dHKuy5LPFH9#*6lK?fZN4Aujo>lI9d{E&e) z;}L$t0gt}j;>-BQ-vAZ|fZh@Qrm$|AI3JjH{de{vQGf~DjdAK{t3~{`D761h0Wf0H z82%zO{rlJnoR;G{;q){9>e`5O!RPq{NBhhiADBtkab1FCJ9cQd!+{qCcH4E@_u!`- z!284<>67={-f&1hK<78~hSbSp3(2nNlK3ZF)qCK|!a_)|%q>&o#TDyx?QZy476dt7 zF|U;P*@`7WExh8tkN*Jg=@06Qd=9dHa3s_p!4;i-_*yQ zX3#Hr-<(qA_@q4^-l~_QyT`-tz1G#?$A=Rt>+a>X-?_9;zJDDx&EW9t==${Nd-=S1 z`4FiGBh4JR_ok*E$(^sRX4Y^0wsqCCzXy-UA8+Ka|LsTAu_to|NZW8@S`-D_V1b(C*RcDuZQEWW1)WYczk~qeZF1uqb!~d6$mf+_<*?H3jEqC;?7Ssk+s%os=q~X<4jZR8`j1dj z%7c2Xf7p5)|EwK&A65H63?KCo0U$CCc-L#)Tz-qbdV}vnSu}cO>EX6}t2cY$QUBq@ ztV^cblN9klxsv%?%OyUAcmLD<>Ks=4r_CP~%X#2Op3!W7J^OKn13o^wy`MLAyJMax zliMSHG!Uipab@uBwA1_fv;KI0T#2-gBlWWKUF|=J>SObErFeSse%LjiT=-w?qG9R5|lH#vMTr-TL`BEE~bYl{txm{2cpjZ5OxM76SEem}gGu1rg3-U}l`fRQIFtQWwqF{B zXuiRmwLFNLX5*>ziSu$zP=wMhVe>ZP=CdTgJ2~ne1%#cMM8T4!?^+rE#+9jcr|o-z z*A4d#$LbBeAavTnK3oV@Ksw%UwW@5C#Hz77IlWWvJLUdrxV%&DIVo7F-1p7Ueb{OE zB^q84cwVhlcNN?_4ZqXyuZGJz4WE;O)f&EG`Gh{fzV7Z0o-Kv)Mx|tygx$fjouc0< z`d7o{<%+&J4MKZRXlHAxXbNU|x3k~Y&R)z-SrY}m3Mu;?H+Opa73%3WdEAq&Al@w? z|BKhUt#*73-vbu@+Qt2~j4aYgR3VzV%8Q_0cglHpI_&@M*u@$Rjy~||8E^n0< zg3u>~IQ8CTh1pwej=8Ov3S=u6$iiHFs+dwA}XCquu3_bBgQ@|Nx|Ue?yGZ+PYV zbzAGMPiu~A@7rPi@}$=#&3_f!f0Ir6p_pSW@@BatLOYh7WBnFb3tW6Zj&%{|`VF+H ze-&=mJ9f=S@3&KZ?3nF0e6MW>fgS8)BRKZwjT83Y?VT<=9Q#Yyxvh1NY`=T$*pnTK z{p**tH56N@Fwm@(jIz2rz4t~KyTh-o0T?(cByF2%G`PIf7#v=rD6ziIu8 zGyE+<%q~-yX7Xs?9NT1`$3y0V?pLSy$|YI#d)(z;{+AnOQSSRm6`Buidj!o2F=q&U z)8D5A^uoXI6%5!bsw7v1D!4T}XJ_Z^ycTPg`+2TDdu0yKw)7o+WZumECR%L&#wV|y zlFj-qmH3m+*{}3dw-$CxEI9XH{D1$RLCD1#@6&q)@b<%g!t^@9?^f@9?)O5WX+fmStVkO8lfd(Y>`gFs2hNu5;omb%L|&XjUb{%q&V=nEMY~AR8zMA}M18k|eckKWTBqZ~sqPwaH>h5FxBbP6#YoGgs#+6wG18rW+qot0 zC4%;jgRJkitQ_p4<+}Fn>h5KxP!cM9N!E9@Tsu?v%1z;a7m#MYA_y8U>>SsfG{0hL zE+omaR5nyY*h%tEl3%AJ7m;FF5!JFL?XHmAN%1R`;vy2P$VyGGO5#p}cM|-%B)AZY zsL6)Fn^IPK%Oy<`D^-2#(#sZ+A_o%acjeWWMJ$0=9$F7%HlaO)_r}PFmiK_a2YqW~ zlWzS=brCHbbyi5hHnnqpKF%uwygF&{Y!Jn79LumJsW+6Fo=R$ zGQ=vdGbIcqsiu@QJW@UrhUI+EzJq+P)daO#rQBK}vs%DSy@+dDLoTpwF6`S}%L<$} z=XyQzuM=?Ovtc;p0%tIC?R{>ItPW)ZHa4XlZwrkui~FqFa(!!Ta~AinfBw&!(^|K| z!HP-kefZZu{~tHNln@}xkv|N`MAw{Uo6IuU>@2xGsDtC2E->A2N5Ddoe+*b{G*FOu3#Na%3ODXE?I<7MUXuF7P71ZF616CGtD$dj7zI zW_(0`TTzJQf-l1QosMmN;+!z0(W1caM(&(OTdbz}sv-)enzPxG!V7#whIqi5YI@#0 zkDB3>EQeXNBf>Qz4I(pY>@zLTyB}EoJqk4Wr~`(i3Jf?>c4#?n0P=zAkGrsmVUB=&IljdYGZD73N3rS56G|)#vRae#p=+Mk4IQoA z%L%ngkZz!mA345ChGn%}7MToxA##woRNS{Qq1wOl)(68@ak zuvr$J#^O<^2716f`HY8meX9rT3Ou$mZ7Y}}WyzmJhYdX<+@62WkypGSAr}5YBrSA6 zE1)*J?33@2|4sT+Hi*Stn-{lS&m-0swmpm#(2W9of-!)A_JakJ=Js6Yo=QyoiZfg! zKz(8Ye8TQ~&c{vQA9th@B3&m4J%37IDV5HcbQCpfX4%vv`VK?Q*$o~Kn}@A4yhIdy zhY$%Tm*wq2YFbYb`vY=kV3EZJAfYHWiACvnM6(j2;>vydXXN-q8wa613V_TFcB2^5 z*CqU-$`jQ!QMPv5A}_@Bhxb8wk47%BE7%ejA4cBmc+9jVk3#rPEjbvvL==#HgmZ0g z*mZm+<@BwXgCHo$#9B7op@R#e%np6rCuE3@-HLiBAmm-2QWMb*l`i-cwZ|w@1&o2j zi-Kt8uZopLtt|0Mt)Px$yJ`&`SG8Sl!db|})p}0U6s25Z3Iv%6!ri7fcEh>}%J0E{ z3A=0*HQXW=Q)YyA77+$w7oM@H#~?S;LCYh|=fE71!3n8}w(BrO=?!4!h~1E@VCj1v zH}L`5SuXqw3!7L)*+Ix|NaZ3?c}9>0?8v@@>_|~>p=~}T8^1y3(DSWPpITXhP3R{b zckITWV?Hf;?>GtkNU=p{-++}d^+36G)3>P_qI@Cp3cRwxp|Cmg@`liuFqG^I-yv!r zHi~ae@J-4)@HzlB4!~zS@+M>$M9cw_yGv|8?w_&cJ6;qJE?WU{%?nVwR1vD0UdbaN zu~ZgJ9U^kSnA*Y`)Ltyf=R-~sxfW461L6q*^hC5x=Tj@Uf%tq&Y3MaC?`p<@cu*+4{3+EhVx0LeS_DU`89dA10*?onOt z5oVJAhD80rLp=fzsGbKs=z0NczA1Yqd^7X_gD`J^DoXNoG^7Psuaqj)N?8M-ncip> z!5|v(6>PMPvc4=M>t6}!H9 zoiWH$2X!q%Boxk03~$WuCUuiQ4V*;5+{B)&Sfh&SIPWh!QPQl9C6AydZM;n*ZS$ zT+gfcj?+P1a6cqwzfb;8w0)OMpECAk0!n2lqozQ8poRe(0#oV2HOW+oPzz2Nl;sa7 zkQj)=HwIn+sC6(P!%@qEQbrXRj&1b-H%0(B2omf#1BduB{aDckh;E`0k(>^gJmLxv zi1(D7EiIRu^QF$4IUxOXu;+PXEe2dlqO4RUHJ6Aj%O#~;Rc2BD?d#KGN|D$o+rh{i z4Jr}q(kCFF;*>aoh`^3vITV%|=C+u73TV?I){{vS2#nTffRmT}4bo3!(zl5B9JRrt z&Uhg~-;3N1^<2<0f@l&B5*K(-jP$%-Gh{=6Y|B%aF$>h6fbhQ*GeU3&P@qLm9*}KB zDdYh>Ab7=VJ2@Cmpru2+e}v(nUVuYE7QO@FHFVJeZbaO-*juB{I8kj=vVFTrYM!c!AkK=Gz~=|s3gO)XSrQ0YxZKEZ4Q3q zEchob6$}`FsqO+AHBL_%Rflai>LgeczEH#_F9?TwPuAoXH|7*}gbc4_EKJKInjb;{ zWC_d(oS+JEJyiG%qA>J?GBkk?%>##oKeXL-GZ@DMVxvyU z=+871t4M;rT(Dn2)`e=Rq8VB)Ey(MpQ4vHqxxc1;Vrf*bY# zPj^v(GT)>rHw;+8s96CdY$EE<<@V3aORzO3LECpmA>#>s8}cBKZXkQf=|MUIv>DZW zkv}5tRKb9RS|b`s&E1Y7@v5$9)vR5A7V}cLW5aeGs5`V_vspx+Gm^kQ0^Et89EP9p zQ!P6H3&6rQEfzTh+~(C%B@k^Mk6YTFqQ7O)J?-lXre`*A1^ElJ;Y zN8QN%uprO;Y@mPHk7B?BK2?VT){mn!)vDx8)N%nf&Mw^u_=u2CQc2w`;W- ziHIH>fCC=1?I8qOKx#3*H? zOlD)b$e0*}F|IhEy-+y@DD25LNyTHmc02OHBV;ZS3?G7_p@9J#(}g!67&PoOBO2l8 zgeW~!xn7)OgBUGEZe#^xr(^q|^~auz5y5CQ^864{2Ze~R)5sf;=F}ROR~P1=%}edl z1b3Fo1;&(-q5}{vd*CFS6}Bbp{t>~4Pb?KFy8&5(CqP96JenqXU=bZFw5Ub4A3zu! zfI!R~r@j#SPLFUg5tPh|pCNVyla&H*DuOu|5H(7osi@fkqwVUSO@iBncH$F}L)25? zaf0F|%IFjL$QbU-VNt5UvaxBXDZtcH%eKK+4XC)X1UeurmkgCiI&r)sA6*~_u+fc? zYyh&?M)QEJnvRd1~`4Wd0xm z(86>)0w+);jKM*yVAz-ew}&=8YQf&GJ8jXZd7;-nDU>dai&4`c@f8IK(kZg$wF%b3AzTq)X$_{&Zh*lw2wMrmQ*Y?%{csoLJi zp~~KZNE>0mEHeeR1T;ueGy$YyI{>BtuM-o26QR(KyI?33K9!@@^Q$_WSc|n^xBZ@d zJZ!k+^+ID7^PJfZ$9VDtnnO{j4biTILID_URwa;*h|DMsGzO|S8F9ct0Oy1iJfJ>z zs0<7|7F$G!=XK-!T!Ox1%J0SLwuv>2_cc_`rs5s>m>9;v!5|k<s>o4iqaRK~gsc|Cby_}fDK5|;>`%bn-r1pfW*RiP2}L5{8F}~Q9kM~A0Baaf z(vxbQnBW$A?Xg;mhVTJmW7*p|LlK*4cp7yqhzLzbaae#F>%ant$h5!`l@2N0Au&Z3 zl?{XwJ$pDS+Fbg$L?IlX#xGgR)GRPkk$6LtscBfedD1Ykj{$N{>sdsfGf`)#H>KV& zUAb7trFH-f{@a~TaiR#S4VI4hYz4Dyktv-6D*2g8=6R&s;H;ZShYKPRYd1b9IOn!Y z%qKMla8?ME5S8DI0M29%#Sqwrv3kOB2*1s|sJuc<^lhvZnUH+S`9%3=5s6PC2k_qk zSc>e3DPL--B84|+x`Rk@YV$1g^`F7sXHt63hkMo1@+IpxEdFE`z?o%9)Mz$qn;T^L zL%$p{F*+l^P?`k9c+}A^dO2O5y;^pt0}vgADna54iqKzqWQrY+nm;lHlVs3)$pSIH zuxd#uBE7aHeYre0&bj*eMK+{%un<=%B~g}TJS>;SRkEhZnt`SC$&!~R6SHE3xGK$Z zwv|25Qb9Br0Jvc)4$E694y5iqOJxsO!84R$OcV2q@y;PbMky^@V4b|omvlqSC%R2j zqeCOOF;yiC{>EtmwJQK3*uxW#VZ3PEvyY>74HvB+euF z5Rm+{=rN8I-NDum+nzu2z#u`An(XCxfc-*6C$b=znwmSd4gon`&()2;aVp1VJGU8A zIB*D_8^o!o)}v+LsrsK8BAoX_5^RWm2A)}>aF=={1+yf)AXh6DGp8A4Rh7glmN(D1 z!4A_d;70bHLfl|Sx?;MnI?{zZZ~jOZGlq$M!9#G+a+GV*G+Rz-EjYoY#8026OSvjm zl^Pb*q%C8$!q;?uR)V$^My)HlY&l@W3vf?Td9hM6a>YOk3%EAAml*dj?C}KmAZ9bq zJ(caURokZ;IW9E_)KvU|p7olhZ00T#9Y7wi$>Y~F^74|2(K!))@wCjclZ-3pyJV~D zdbw1^(+TN9OQWV3x-{F@FPGk>1s2Ics}^A`%PM)3;ku+tMO-(?O=1JP7WJ^{aXSl- zOwox$!A%IfnpbCIy&{=w9hD6ryEzq#0fN!eP&9@d{VF&y6`6le;{wo13yd8{{yeb;2DiTLz*~ zzX%gy=^~^xE;FNZf1zf=Z0dZON7m@=+Yf+oJr}c>7{0vh&q}}?JeUWeTVycp!IGKG z!9sgiQgyW|s<|7Ugc@Jcbi5(_H98vdI*x`$P=BW6>=VoVy#SY_7{5WR^eOID^S#b| zo1_YXNb$fLI$b+}j0TnLjt%vxEy(_`^jKVbN7K5FAL}w_QQUj6T=2Rq$x12b@<^tn zmc?4OIfGu24QYm>Q*x1g=NHPwMsVhfRFHKK{6UV!45UdE(5U#9{_o6>r96eY{TH+W z+M+s*Q!9zuj0G^M#*bH6DAXaylBSq>_e?D7N=d6^OAz0Hq^+VHREt4AAcPv+Sy>QN zc{1J;`XNG2H$(s_!KOCsbREsvVJ(5(?K*9O_recLq2FSf%RDcNQvQ-*Ua6E78G9wJ zNptqnQ-nB%W999T9{ywSYD7KGm@bJdrK)4%tp5ZeJ8{;q!#v9up!elp8e0u%O@^G$B9=8W&F5qzo)@h=*(Y zJlhPzbaUsn789JXRWoHHf812zc|)dhupMc9kxs^&=ANsQwO1Bc$)0ra?H-|s4JR)W zH}kw%Buyih43)3ea-H}InE({) zgcTqA&eN`N>nrMEL&#%DAXS?%4}b@Ye)LkdEw*b(j=florYKcQdQHw-GTzW@lD5*4 zWhk?my;?lIB;f&#dE1Y$VKqhdIAoMY1JrcBT=C{D<2p90n z){JURkY3=JY*mHE{TA1%hp<4E#_3e-703?J#f7FX=hy}nW>QIQ&0K0CR{QkOQ{1AD z)i{t3(F2RI_nMo?at#!INeXxWFl^$o{PAomtQvc|ak{)JCUuHb9?6ns)(BkLq8%%` z)8ItUFm#gkV=T`cqYHl~uI3OcX-wOD!8Sw}{aq zyZoeJEgZa<*d4672_Tu-h#}ZI+u+MT{$r`o^$AYu^st{YPg6`I2I> zI8m&X#OhpQ>bD^-QmrOcs&kctN?EFvwd^_TH-lQg)F=QYeij*{kfH%QD z5#E3R!E7DwVj?LuO{l0s&JBAjfTL8B`LdeRX1pkut2)L=cYx#V0FI$|7&#lXifc|I zBHz~+Y@fe)GO?g8fHOuk7^Q$j5=q$LbTN8#oV6N-ayOD%t?EXu1J@hTCW2^GN{XC| zatM;FtEF1Dec0>OCRjVe)ISFeivbQ~tOdLQ?HX9u(IgHPw|RP9*cPhH;wIj-tUZa# zVBYruVi`Dldq7VYSX{^P2^{v|RCLlWY28JIlY~N>TNP^`4Owv3l@t4CFb1(n=N`98 zBjlZ@B@RzA1PoBC3Wl)qq~?B7yg=Tbo6d?aV!j$mHxfQ1shqUP(c`m_OH6JZh*R0c zLdYdv(X>+jX4#TfB@5HHpD8X9Eyh{QM~m^RiI6P`44*gC8JtYPR*o6*qf^1&q}G0( zY@%q0e7ORr64GQ7gpyjWZ6+bc76xT`b8TkWBCwXyw%o7}35Cq+;=O^5m7v~Z$l;wN zv+{=G_*NE7mDln$H4-mL29K`6rWR|pD8ZklUQV~1Ix9dRNhz6vmXGa=T16Bp*tLFR z(r|-Hpobm$EY5YHDvIOwn(PX_fStc<;Y}UnmyQ#(J*?q`NcF(;ZLAK(OCFL6`!2Na zC&R@{PLv8Z`Xl>@*3ZDJAWpQ%3>)?)YN$0$S_?i;ws?XK6j{tA9Ysx(%DTA{?Yu07 zwT{63e%?ZF=PeZV7A_JqvZM&1KaejSlm=}MZY4sxK z(`8LGq)PsZ<5I0wmMW>k=o`>w>Ac#S%A+FlrHY&@OOeaOG?#ch1-DZkPwkVP^4Q#D zHlinYVP96jqa_2`vi_%KSkK$ zn;>Z#lurJGAGsBRubxcSWz3z)NX}y#Ordd43w{_h9W6j zj13hpN~Xo?^RA}+vLqf7?1Cap(!bzyhGorz>T4_smS3iJ$-Fw;A4`LKpQSvEa56`Kssh=WT{8;>N;yFg-9qr$5Q zDbvI;E1JNUvn2-W5+~8Zvyc`ZJ13j19mYq-T(VEw)3!@HPQ>@p#uwEF(13zAW6Dt> z39%6&ZBlhTio#EE`^t_-FUquB$Ry<`l_(XMaHePCG(KXU9L!C{=i*+^`N9{rPHzGo zW8vYL67iz&KNp<{Uq7RhLM3EM>lU{y&Fip3iBLBWN9&88d8-I!MHX`)gqOuywSrgT zY)X!nB@N>X3rF-mILfs?gh?$I&n7y9f!%@T;&4|-{>m2oD;jpl7957e@-Jvco{|7; zqp(VtGC=>CJ<0K~kd<7*#_6tY`DD)hh#(fYe+{y2rvrdVG6W>T{90yp}jd5>w69t~gpnc3^X2tooxLF4!Gh|NL z?WyGde~d8|2l@dvr#+o`eg|7X&_{Rhm^Ih)y=a6NMWuV?#a0du3(+=<9c*MvcBnnF z$v<$59nA5FZr_QWXcoF4Fp0Cr!c#{c(Mc`Gb>JvI2JGX0jbu-RPCIffTp+IHKa1F@ zl31!rm~h*cotkNojsun*noI!n1-4nx4G*tDX+aB029D_!1~Cl4HWir3!X1>=i&Qe> zHiwx61v+6ZX9y>Lp|u{j7dP+5Gde6^T-U*M_yCQiLl6XqeG%_ep$*#u%nH*B0YXTl zK;@XlMIrEE1KIM4Jq}^8bu=m}PkO5z05GInK+mkD2?2lSQ;L+d5!Fg$-+<)P1QQcv zNQu$$$nvOdOz2K&0j<&D^GOeUU;w*_y~AP{BYH~-yF?8>5O1S&!ZKq0lb%t6Cze!M z*S3kUD2cb?<*al1?*-AWT!OO@wB}Vo((n&JO%1LkNrE#+cZJ)E)K7F8l%9DhAdPl(A(whMk;5wOl zNv)PuP0e4Fz6D2vR#ze5IVDC9cHmP5+|S+|D_F3F9kcvt1-H?&#A3-dGfcgv8%2)b zZVkgmL#!dN(3~X~E<>*ZCWjQ6y8$tbW6N>z4l34OV@;cv^(WRoEwhAAti=f^kTvIb zXorp1_HAQ^ptN2vH8!KKOnGc>V)NHwE*4t>t``;gzXCU=Zjx&gm`%NP2IArF3F0Q1 zmp6E2615NP_MV{Kx7q!7v7Z(LNC!G2;5|G5d5A?B!M!2`!9k}%VrURlIpE|Z9tL|L zTyTMydqi)|i?0+$(B%zCRCB)lsy(u`ET)S^U;iv5rGC6JZ zk2tPrx4j{~T7|fM&><-D=@r|bVgN{SbAl+5=xZ+UC1`iaW}5M`GjN7X0tgNufOp_v z5GP1+5|Av`Ma}VakiI|_;c0=LAnI^cGGC-S@HQ0qF-kS{3te{mTiyYnKH}@DP=0aK zr7g(Oz$W{H$j<<7(#wU;UP8RMKwyJYO&U3VnBAU3E7swPI=ngxuSF^#z8)f9SLS@ zU>|}f62scHaY^KCvLFWoiE%t3>qxy|KW<}^vAan_qHLm=0m!z;T@(|6cM}(o=I2xX z>Z{V&dr)=mMZH=24g;GZy;A}1ljVr*8FrwXH`-bPG$QOM3-`?69eZI2&G%fqbiIIP z67;gd>r$?_>^6McwaZ7aY3+Ox4bP({{6J5Fbz>Y8TtV#tg{e6P#Scj((&Ts=^8}Jq z*$`{hA_vE|p}N!SLI)87khD63DH969t<2>ncuQ@JO+Mf??ewk<`Y#PM5=0tA1Mso& zN>hkEIJDtu(%B3}i@jp8uEil@IU;z@7<}11yW55O_%Y;P;6d?eiV4guzVI3bh(;aA zx}f_LU*I@_SnmsQQ|zg2a33byDu71@Ng?f?z2Dn^bvO^s)#2%Z&IelWl9(B2dWF5v zKH%(0${NL=Iat#5v}K8jw4PCV&W=J4f;;6ACfswP?poDxMoR~WLvDczZZnu_H#qQ z7^#S8Nhwz=as2hrPW%|VbMBjz70FN=R2lTjfug&nq?){`t03}Qjzl$Vzi*MB_}$b>tie&x7ey07w5X!Z2p-m_gJ!5^ir8vn5|Z& z6(doui|~s`FruN@+2zBpNmC{x91j}{bA^RO`pddEix7LXxh?b|rJ4&!TiMVQO;B=p zM$&5np;opqRnkR6pS7M@Ep2jDPUlz~y3Up)`4Qcfi1@kjxF$07v;3m9!;|wM7 zC75~((Lb$mOyCp2B6xS!Y$>lVj6q z<&v2zWD*5Y)pZdP*t01tykxX$O1iud32pUGPYI7N%!d~7I@Q&}an2gs5dr8rE|F~D zuy$r}r>wO7MR#eMmp2zSHW%_|ie{mF$ID6uGui2am6E@hjU`Q!WD##VeG0q&HP^zH zM6+6!D)|;51}{qGQugf0a(I({Ck<~XowAZ#EmgE~{;su!B-qds%N1GTOKPsngD;D` zSP`;&XG2dU4ynQ`g?gdoo=A5q8~ORufJQ2fj6zy9K2E&Xd?as9pa+^L%3)`oqcI3~l=Y^?1dr^pgvd6VO zmzdvJ9kA-W$>)yOKJ*>zjOGmOh7-2?&@nTv-J|QqUt3;hiocMrM3CS7^8W<@00960 z0{~D<0|XQR1^@^E001EXs!}E2BL)Bfiy8m`6#xJLcW-iJFLY>SZDlWXXk~3>F)ny* zZ0uQ0iyJo>z8Cr*#M~W!&G=)9H`rr4X|tPz>?S2gc}AX5tQi?e-dz&{B{>!frD^G* zf!=y3Z37APlKz@)Q`!{r7pl>CY{`~c+H7DcS;JZ%&-?1p`$~^Ap37HG5(178zFEeCJ`(-0DxqK-31+_|#*;Rl!Dj)+l$fV5Bf4(vFw5YwG< znFq2N@=J7zQrS1AEP+B+SyT=gJeGbFQm$1hjdB8U>VOm`((TRh7zYR(W-guG!uf#6 zf2rVELqOO;<{B9m3L8$U*%zO4pD=I)Nym}ng!K3jod^dI$c60aI2HBhly@(emqtWv zvDNH`>ThAUFbF3#Wyhk?($IB1x6xhtR{$b5af9ZdF=#CPDF6%tIn$hrj`e!&Uf)|_ zHBer(`0h9R^{Ro_{MR?|T`&6>cm>Lf?goB1^j9&|KzY%##H3mCT?4N`d9ks9Z&tc} zw`t%NC?+_aY@jP%qwW{8T|&l`T(iR4o;z&T7Vs6StgWuxJ{8;6S_1D=Hk1IBTqtk~ z#C(dzFpxMO50MXv+8i9xyx= z9p_`|)zKhtUjF#z<#TZQ=*8)yUrrxCK7I7F#p)VNBi-ut_h0<|^i%NH^Y30i`O1#x zI{x>cKKt$0FYQ2~17Cgp?2i}EUVZcVpFcdYz`G2Nboe1o5C=EVF}O_=IWfxsXvF@V z#bFGwZqZF64pW%fSkMCz8|WKzNT3DRLuLf;GAR=_;MLiEgYZGjW&&G)*W<(h?o zlv(F=JsV4p!YqyKgR_~AyA9!yZESxnzk}ISu6Jy6>Bq>Rd5g%D!w98FfUJ*B5VDxv z!`O)HJ`Nb=bS%I<40;e-Lvn~m#%fv_UBijY%iKm#=G%zP{$0?c#OA(_j!dN76_D6m zB4UK`Dx3+JSQ*2F=*T@NVjIbWISY({b0ISwAwt0*M4a1fZnC*S_qvo9d)oKOJTZ|> z;E4^n2PxH&eLC@Dm`tslaT@E`kN89`Q3!5PVI@J$9g@{$9$>mP-FGoE(tY;cz9W^@ zzCf~`8MAE(M9uv;Cu4||^YWUWCpbM*+}C8?d$+PKh4j_8Pwl#UAqBn5uyseR%lJ0j zx_JAPh4|uYcOTBuTS#sf3-rBoznAX+y>xHe&*v}LD~(o{_~NRr+$3A??PE;HftVAt z$CbKsIlXWw8&yRaEguu6u`DizGjI`ustgzvpI{Lm#BeHysj3kozHl9JFr{3c531Sf zJL@2`WS@q4YqeTDnMwmFR_*e9Dca>}B=XiqbM*+ebgycWdQ>h5WVZi?gf^H-qHZD4 zEE>-sp=Ne2EZP zwKJ~glWBv5w%kk-+A@q`h}N6WIoIt~N)38hC^l&{Tkj&*nTV`u14+#qIF@^^?nyTR zoOYbCycx=3GL`;#rkjvNX~zk~BGCUuS*DDOKICx@Q(gJ|6D$w|1SfK>>uDhAid(hT z%;tDO$+asNFLLTjPcj;hQ6RRotg5mvcVg{*OK?`9GfDa&4v)cz%-C%x1LZZVSpY-K z#WI>9X4>LjMR8587F*Hy#cZ`jA(_T-aiZvAp2JmnNk^N9V!A#nv%Z+jD9V!b)~D{- zMp=g@np+N#?9{aD$Gbb^HC8L#H(2G;y{>ibLhH7j5^sMXXw+7N8Pqby7HX?jliE36 z2(;mrOLN;9ao(rF`l`(ih~i$UY8$U6=sc+`HDd-rw(?Uc0(ackN$!@7lGiy7t<&?!4TzGf=-_ zsyLElp+0ViZEL|QTLS4RUJcK50wzV9zN zCK|{Ns$qa+rBZT2rl&Eyy3W&Be7=U%!%c;bTBy_p z+~DOoxAD+6J?WH8UE{YYcC2$ebhV$9gnzZWD`aB;xEQ2&xD4#uF(By3C_eoB;D zbxpt~JC?Vu-b=vx?p2_Rq$o=erQ{+N`QnF+XRw7Y=x>|-;u9nyo=!Z-K5k08D;hQI zDQ#VxEio94xUBjVl}zU#d(E@YTHgDWs>UicjNB-Jw8j1^|q=9_S45h~T1D2ajA} z<6w3;g1FsYIG?{%Ggz?Mz1DHOiS_FrC2*xz-)>Q%hHoc7>N1&(g>H!{xl&qv)? zJ~__TemkNml(aR4#3~aI`pBRBY!2x|K*m-Rme0gx1HYK{;dd#shVuMWw{({YT-u%a{^%Y1L9M3tYW3Y*d zdR!q*y4~<=-J@8cCus4}RKtVd!C9MQ+BGvR#AB~XIu7e~u|Zj&rw^29umsQR{h)!_ zaE+ALK-$M6oSEfQtviCq)ufL5Tq#z0i zBNAm&`%0=?sD#bB3i5pyZU+;SdUKGovja6+{LZO_A6UEZpikg%zWOMhOb;^q+v!{m z_KsTf(2BvL3gMuRCP(iB&hqAY5dhJ z`UTkpju7l9l?7YgR38V5wL>t8G;Zb>7|@r)CK_j;8f3=y5cz~YbCk|74~(+xV4_)~ zZ}P&*SVxlt_rMLa*CoPfr5R7aVf2$#nW-?9BMP(hzOHk{N|C2}ZyEA|R!-hWT?q00 zMHgBbriRLp*|04i)oaWKI?^T~RN30NLRR{`39Ju^I|N6NL#v|BGe=c9bO7}Ru5U?6LD=+?(*%~<(}RrrC}zBS%p)G!lS z^buQl)+Jx2k*=`roE+d5>_HKhqLCTE5~%kkJ2a6>_)xG{T${3s+zA7pIBV$oRfSnKt?Tt1xIa37Hb^v%92yTmfZ_UjsZ5A*r(es4K*~m>d6b zf2!I5nOyXUl_>Jy!!1OS-rK`|G6Hv;56mX#94MI^0PKVWvJWh#_9jR8Fv=v5#~pz+Bk6%)`FQ%eB!9+;ljGY0`^_BtTxi(v3q228%)JkLSB zpv`Ao!#BKQP()tZ5C6F72#SKI8ofZvFE^&G8JQF4CloxSO<=c^k8k31RkM95=k)j1a_Be0}@ zi6EOLK+jq~rjx!2AEj$%01Shdp=Wgi76s*tE<8Ex3ahXq&mLA}kv`3-syu8(5YykD zh&!O%d(67=0JgGQ7gB9?#;;0x%U+lTr^4;kEAGo_EpCjpUaE@vMhj?emNJ6@BhhoN zjSs3q>rf4}TJ8Fol8;GroB5~mLR`n0hfl`fxBBd@)DQZ*JlIQP!H^HVfeKZ03tbd@ zWVdO1Bz1{hmznxpM>rvxl;_ZW52Md!S|<@K^_2suR~(o%;?y}DA2Um$#9*_jl6gNeG#%18 zv3yq>EM6d{lapPQw z-?HoQ*seK>Hd?2S?EGnlXJu$4_C5i0j^ykjmQ@P78V#VD8 z=R`NvvOc-X)ymL}@DaytO`i+F5EY>hzE~8r@I1c5UqKZ}Jc@M^Z8D9Li-u%{3nG=< zsfZxXx)y3M%3_)heZA!)^#gT`(}nZcdyp6IR;Ss!_fZUwmZg2ulWcJVUto3R-CL=~ zf~**wLH4_zcgw+mt~c3L&U&q|5ckHyo1;{S#YY{h;s6zV&-$3Vm&wVhOMgnl@{T)4 z3eT*QsIba@=(i|gIXC*#jTkR-;P^CBto;B=RM@Zh#3V*H3ZY@K;&nCFCdbGl+C)JX zX7`GS!#c&lJ1I8Db4A3Ac4c8xhUwVvo}IpvAKcK>phaabzVAt zv;kf0f-ZhogDz%VZTP3pZQN42=hrATS(3YHb96*I5DVf`YK&C2f+?W*(m1&!pi_8j ziu_y|hbhio8bbQs)De%eDD=5m9hG&3cy&hX0|^Vx)bP%#XGq_Fbwl!l9lj2GPs+}p zF~%li_Cx7Y^bry@^?QaDfEtpggBVeIl^Fr2s_uKocnryqovRDc*AjbQiyFSm&7GDr zZO{s|!cVR@qoDw*Vn_}`uf9b^NX(or2pR-`f; z`{2`L(hA0K+#h7!xN_V@8&9Q^RM!%J~d<<_`#WsmoY+dD?08+glpUB|k zj`gGT*h+JeY4U^03{GwQ6ph&@vV~prh4pdK>UAPj$geUfxg(v~xQAZ@1VZ;q84DtY z)UNDZd#PtmE4haxWm^hjk=${aUdCncydPR~U>OF9pc8D=F{&u=8cFGE0=L`qLmoxa za@G2D0WSFQ!PTn8Z|{+>x-o4}_~sG$fi0ndRroF19SiY{=S#zHR#rNEc9&~1C|QCG zmcX!S2j^yRmC9Z}Z^COoP0BzsxS|pW@Nh4}iwz2+IpcEU7}=x26U5!JvP23~7#40l ziDs!qlKJ5;XbW9@mSsK&Z6sUN=HItP(d1>|KYQHNV<&TaA(x<>3DSa`ZbMJ0w@Nuy z^O5&)Eq?gQOF<({e3Po>lc=Okb%ey>nI_+bm_;IPdA|Pswjv;*Y&|&33UT+qh=DXd zA9#jV%r-i`kHtH?%hP(R#EVsB)E2WuRSCvDsR!H+7gT8emp57+rbL?jO8Mbfs^9;~5U&z979BS)T5jhpBy-zeg zrFh00+F~D*Y)m3Xyh_6v>8Bk$+%|qz@6o?8=5VL;Xon|ePi+*A z@60|ni^-k$H^by!k+@WUSPGUr7p+uOYw+tZZ^Z7tXo!GEgP%eD#1(q|69%(TaEV&}E#!aTES?L2UfDu! zS|Iu?7=-bY{_^}EPoOYgVTNi#{u46^8y@;Kbz(0hbz%dovUnAU{*KMxfl|x+HLCob zhVh-O1GKz-_6H{K=}FgK?-wbxVx_E0u_mXfT7lxdq}1t`kAaYeF*=(8Axa+smjQt# zlt%Vp3$`D%cLy{{`U+SgVyZ9K3S>a5?4Xm|Ft|1PNSCk3)WnE_K*leW9tAR{Wgq2DY zjD=2<#Ec^S6nzgPW0Nt)DdOI*OWosYupGU&}Nk!m2D^n=O))t}F zENq4G3ADf7GTBIP++?WE4utOtHEh7Th163d(IE(sFLI~=|7Xw zXwh$Gp;b3!@t0%7&Q`8r!TihlGeIE#8Zv)5L-{=_QZoN=h&bQiGhP3@QTEd@QQ9od zBmbB4E~m(DTCwCWCw_ja(W0pTU(UZGAzbHpsr&$zpf`vde-==)ONe>0HbayGd+rij z9N~NZbus%&?`(>qXv$<>-{~78KmpE~Ri$%!df<^qhP(Z~HQq}9gCd?M#sXMW z@5Zzz;$k<=kMN@pET8b>w>)T!F?%Up3P-m0W#Z4_RF3%h`BUX808@EFS9hgik}kxq z5B8sL&*&q~seM#*XEeeZH0%(iP0sg;uYh#h?6f}CV=Wa5^RdL^;G~<5y63InlNxHmT8QXfPD2t>2_U`2(KMc>` zf_Nlh5z2jXYES0i`W43ttJ9GT2ghbj_lAdu5^9A&8b=hsM6KXFkC?A@AHzLi;D8Mf zzu4T?zZ*=}?oN}f52FIOp(mD0I=SY*Wa*;$ipc+^r~3da%j6T&4G+3fSTg@$k?7%E z)~INtY-wv_Nl2?e3FQsx^Qbkg=e>kiDAS4gY19^F`%Ap&xJNwku=9>EX)_kE?w<`IsiIB@qQ-%j}xVGJT61t zAG+3fPIFRq32MkHHaz0VQFeNgaRXgZ`{TS^50(Q)cw6TW*vNFB0M^Vn&W;JW^+4;= zV5})xe9v6f7ZGcI+i!AHa7y1d4*5LLie`(p!XLnU$`1m!Bs!>T&&3lIuh3Q2RF4?} zDHmIdu{p|8fof1zj`2WXnOoXChxgFe=LBoj?X~l(A0b%9*J}tyL<)FNhq9YfXlrMW zM(!Vtxs$*+uA+}4a=JM_+TTK<7R%BaUQ8fl&dKOB(XbY0jyVx-d$?`N*2vgK;!!+o+7Zrob5;uE40 zYY!;-z;Mn2&_)89GDj_H3~-I1;Xv|;@-z0B2Sp3bz-1VG>mNt4FH99KD|xyDty74m zXm+y066D-Q$*beuR&3L2>t={>spKevNpIyBhTy@U%3pX{c`m_d9vEF$EEVLuVSiGV zMk(|$vuIkyVF!K@isvD>S5pr>4)vdxh0JOAaD;c46F ze-2YS3UIl1f}T)ltIAt~% z$nXE%zsS+5@GeFKsI{lHm2YN%^dTdt#=jEc)-Ughd3^7dggyI#Uh+kR zoiQR8P&Pk|L#vj^m^>)XrA}p-=~#ViMKPu8)HZY10lu72gcRWCiGwHq#JA4F9+l(L zNa_NMv;85+85;|(pz_g(I;yzAO^9g?vTL=tC>*xg@@mp&8RCBp+hA{cz^=f9fw^&l zgQ0v{i*dAoA_+|grUXIUc#B7&SSpg2JH1%~y#(}+Ut~Z5e%q6dz&#g{a zux$8z>|FK&*V5u*bHVHu$g8UJ7DvCQz7nj+XYb5qngs_oqbsgmZq=^~S>TyJOPe=; z-o05%3$ahk(l(BseDL%B#<9g|1w%hHf1^5}C*t#&Rg-uHlignNM;h{nKmSH6OK+LJs@AxQQzSMhX(Fnsiod_+^tX*3YkiEMUny7E!Bd>(A0Q`;wbIde`O?*07OF~4lfKjQ77?$zg^8^tks zxNp^bmv&=arOh!|@=dtDk+$Vl9?@)ZOA(>)vBna#t!lX$vxY3u&c1YZ!FZ_K(vaDrV;|cL0m4 zCX1`zn?B!J)bn)VbE&Rz#oZcTH$Lg7z0B({z3iH>4XRNOR(TFq#~UEr`KMYt4~Ln3 zsz~l{<#tVw&A+g7t7@%a*gu&D_>d5nZ2DyUh#r|(jN)v)7ps3_(Y88TA{q%1j{q*k zV7qj8TC-Ss99QzXsb4zry4&-*Z(xg^V^Zq&5KSK89+jrjWlj7jaIBH2agQ)FjR@T| zOA&dOAcUUq+>{=JeT6megk?P0@-RuR(1|2N(i$Y;EEBn(nj6Vga6$Mb-E!&e?v?+@ zk(Hv2U$De+2_)emkz=%1Eo&_t_cizXU8i>8^1K>a*9vUM))tAC@#?VwvS*9;yDrGw zI|Ouy6#h1@zUzxRiC_%0++ePYBT=S6oG|*YnvNJP$Avf>z)!}$fWzs-ka=1{@;vJ>y|--Eb*r7DV|B9JkDYg6KI*Rsnnxv#A0G=!R>=;= zX8;{Lrhq!%tIS!X7$=9=ArlcjzM24Txk_^83pf^O1JPvLnVRPOi%82aA^^Tkvh(Q# zT#B>uhBQ0oq{H4wO9T4~#!WJfxd`0Z@6fC6JrYAT6fCkN85JCx+~?{Vx4XW*P*vOH zrHLuEZnR;q47z2vn+`IsL<>ohqs_(udEtklP*1AGT7=`-4U$PRP9g!`G%D?XJgP%S zM3OnEP~6_5g>O?W#u=AjxzsG$5R>Yx2gaV1jNVjy<@EnCo(GDSyQ)>NYA?QZOB50P=Qz6EupxkzUt zW`);Dr%KDXa3Px5TV=L12;jh*y*JA53%Lz};S#>Gg}dBL7v18H;LFGfp)E%`;r{ZF zKsX`HKx!Bjl;Y-F#GjXKxX?gJKu;SnaDe8~;j3jv%G`IEK)VHohu*jku2Z|8p3V@i zTI9=55!f<;>~P$?!cnG$t3mcx%%k!Su| z%phe9MTLxG6h#Ho^l&s=N3gxB(WO8)1G0spLNbIOjC*#}#pb=;ab#EPS^<0nu6^h9F%Mu{(#za&MHH@ zxHH9wYpjH2JL^I{tHN>gkWhRngfx-F3AbN7P9UxZ^&Wa3Zeki&N=+oCHs+N53N)A# zgcI(}9FEotPoj#e(d34_UPNq5gnCBVK-KxuWSQuQT_K%ERS90{ckKdW1qM3S=Ey4j zu#F(>!F3mn18;|75pUbO9&K|?Am&aW*8NV0cfi&W{gGgh0|iV2Kf&KF=!j?^C(f0& zt(w}2a6)$;_VS?+fu2Hl{`3t6!=QFrCkEWLRr;`+Z==`r{PE2rOE(t^VJMOa^cRS6 zzdfi>`dNA>ag?|*#P9O80wvhcOT-ysM-$lL9&dR61BHm9LvM0mYrx~I=#3)RvWK4Y zUa;?@r|L=S2El)z1RWtnJ`HkmFP(`Wzh}ZY;%l+ynsSOWosIb&YI9E{`N-Dh)~0n- zf>mOkIG z`8$=G+B2lI2jQ$VfBFEAENAO3Qz|1XiNG{)+h@$vVE-DE9BgU;VIL-*Jhuebv~EauJI8$e5D(fg z!muOeiU0bcmt4Q&#s9&pNhDXSG!Uv&{~m_;HR<3L?Ugt2!3%yh_>Y^aRStOWpC`~@ z7|+7VqCJ_Hn7G?F4{Tol9 zt9i)d$XgvtmnA?bz%~1sP^eYxXSDoX^(Wqbe_*mzfeAsIqdxIMK*9LpjmNH*C-ysLt8{jWHH+G~ zw0z=yNfAAT#qf+~L33s{Y2A^tY`G=5F=D2jLx!S6<6Bh~5K!uCx z`+He7U-ssOc$ zmZsR1--eXE;!;_f3vaAKE~k9fDflQWBk%;8yY$~iT+RO8vvrUbpaRnKaKI?R{?-Y9 zYJ>9j09%J&D&d~Ky`ZB_wI z*a~_P7vJ!NKP01hQ+6XEww-vo0GY-3WG}!uS-9Oe7N}A_>AZG&Z%>YC^j31QzyITw z9$|Xgu=aA0Aw>?txLd0T>qm<{Wd{E)HO-xO9!7Lrp>1y-*iEB(Qs51f2aBif+2$4l47$0-ygCdu%2h_Q zR}T14vvE(I!<{$qH9`R|a#Ifi5YL{+Er!4A7+#f!kMdoX_pO&*UgVpR0+T2j5LzRr|3HrG_UO3?Gbj0FnzsH*Ns<=#o!#{>k*Z{q@4exJj({o6|Ha;`Tc2eq)OSpf|Rk)l+;5-n3 z?Y8sv!S{IIkaNaD=MAcf|6NDfjB(U+L8b5vBJ}@i;{QjjyL40rE`Sjwc-;%=0>kAS zN3RtMC$u~xlwD#I&zUCZO@{j(u=*yCV$c4TNGC;fhSy5rJqZCVWnkE*J}~d(dETKW zhlo6Y{MIfr%70vQ6F8TUw-t!I&ODMFkNrJSUy4T3+0?7pSc+{!gC#i;ib4@p_0;XN zu;Z%>Tx_h7Q@+AsKQU*T!Eg!EGFT~$68`q8j@;(pC!*u>Pd}%i{CDCHi5|60L5-6M z4(#vI4ikWlqq4q%)vp#hmADwa07}yd%@rZZDgzfvw1#>WMs8BE&X4MYIwo0z>5x8W zJ+}v0q{^{adZ1#-LioneoKH`d$b>JY0i-{WCoeux}n2siSV)J%6_+s-b`l~A`pvS_iKa+U-rmcg(= z9n-oNQ7|Tm;R=+(!iv^1Q&$rG;W}o8A>3?PqF=16g<*ZYez0 zvTR_pp$xiTvyE^xJ7h-mF`&ZgUANK4!mPvUVd{`8KJJf((Z1QytI}#mPYp0;L}+0n z$yr6E;(t=ybtiF1G8c6k$@S|2gNcNr=8CGsLvp16aWtFjF+Hf!whIUCW(>0` zOIp_!Vx)h(BE}pbaHyXtefuu?1N9b5;1-NVUf?{Fp$2v+b~^b7nBe(GGdGHLpLC3z zdPv21+xBYCswiH2IG>2#(zUAFAZUyO!QYc%J7`6LWIUh*B!)&m-aQ=~zckbLRjJ1u4q~d+>R|ztHNIgCR4OI3-P7(y z2yEdltZu*v3qA=Kh>8X+imb0OUG6hVKJMn*psUmC;lnG7%Zg zNg3v%+Fol$k`>(xA6{0>#Vd zI;i)pD`jYjxH~>5qJZ|RbPORJz^>RRB7n~nKKp>n%#@`KzY?-F+O-nE6I*oWRL!MV zm5^;GOh}181`hcm6Y3Gjx=dHNb%vuRmNaHh$X&WmcgAImN;!X!mkvTOFoJ)ufq~6{;_~a|`Uf|tf9_-rip0;*|0j~E zLD4&v=2u|d`Ix_!a{LXJ28z)yq<>=k9+BVI7yivo)br{O_TN_<{tof`Vv@fh9Ebjd z_~+7+-`RhkmHRil#pGY?|CyuvJM+ISgnzSwf#Gky`iE}#zwCv-WBqPt`Wx--_MZ{? zm(}Ta{(ozxe{+L@F`WG~F8|h3e@FSZg7!}ooAXy-|D~?|&i?Pd^FP_qu6~ciub%Yp z0KfM?|As)m{xcW;)f@et|M!l<-~9QvfAaswl!+WJ^I#@htHW5qI8Oh%YT3Jvx$ zPJ|rDV_oyD90T_E#kfB3%oL*QC$&?+o}qP!-gq%`fVW&JEdIc1qtn4R4w6Tq3>01g zm?3zR0qQsW@5x>HD7WR7csZHwT#cNb$q$wDp=w?R4W*n)nRQc6JE~KOT5i>zHf0kw zwE=tD1|6mC)1H>C=>;_U2(5c~PGA#b*jAxdOB*$N<(zKP{|=a$*3_xrk_SqCq0}Vs zd~^Jx=OA_FfnMRswp?jn=F1zj(euQu-o@MOD!$co>2xmbLAPjC+yk5YPk}v*JDl}j zeAhY8n}x7j1!61C z;mo+P3pU5hG4epij~DS5=P~}u@QpNJDDyQAw+PWx1`p7VafH~CIh@1aM+c`z2kjjE zSJA@7WzM~V8gK+{u&g11PLz?g*M!TeR9pv5@b-V%^{K8)oj%_7qX@Obp=Qwocm&qW z&a?gccpoR86(@FG6D(_(B4cFETHgiQvG+IcEvu-c%o8!&Tw{GTrg|F_RZhw9A_tosWF6nhy2h{`Af_MiY(DQlL zqJqWy9IZT$4^ZPd!LPeL5H#)&1kj)iBc{k5Y!!R*Eaa?v1%aNF8y?I$tm5F#_J|X! z+3X4M%Q_aHJkMa2!f>;dInJoy9fnN4qL)@2R#tek)U&2_H`drEfI6$;nE(dQj05!Z zRFUrgEthM{7J_U>v0W2Z|8+uzvMJ=r@jzA&tUh# zokG^I4yD8U5S&BK89qFrveR8^SX${Vu{le}>~0G4RNlJas)yi?>6^o@*U-4SU0bZ} zI9$cG#G;3LE5xs^y`~j`Jjbe5wXN#cdtfuiEY?L$Kd2Ka=&x$3<8(WhP3KLsoJKo7 Rn|;xvvr5N%F}6DK{te2ra-aYJ literal 0 HcmV?d00001 diff --git a/test/Qwiq.Core.Tests/Mocks/MockException.cs b/test/Qwiq.Core.Tests/Mocks/MockException.cs index 8d49b7dc..e68e0fa3 100644 --- a/test/Qwiq.Core.Tests/Mocks/MockException.cs +++ b/test/Qwiq.Core.Tests/Mocks/MockException.cs @@ -1,9 +1,20 @@ using System; +using System.Runtime.Serialization; namespace Microsoft.Qwiq.Core.Tests.Mocks { + [Serializable] public class MockException : Exception { + public MockException() + :base() + { + } + + protected MockException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 255d5c50..1dd883bb 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -11,17 +11,23 @@ Microsoft.Qwiq.Core.Tests Microsoft.Qwiq.Core.Tests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - + 512 + true ..\ - true + prompt + 4 + + ..\..\build\rulesets\noship.ruleset + true + true + 1591 true @@ -29,22 +35,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - false - ..\..\build\rulesets\noship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - false - ..\..\build\rulesets\noship.ruleset - true @@ -219,6 +216,7 @@ + diff --git a/test/Qwiq.Core.Tests/TypeParserTests.cs b/test/Qwiq.Core.Tests/TypeParserTests.cs index 3d8a1cb6..069677f3 100644 --- a/test/Qwiq.Core.Tests/TypeParserTests.cs +++ b/test/Qwiq.Core.Tests/TypeParserTests.cs @@ -16,7 +16,7 @@ public abstract class TypeParserTestsContext : ContextSpecification public override void Given() { - TypeParser = Microsoft.Qwiq.TypeParser.Default; + TypeParser = Qwiq.TypeParser.Default; } } diff --git a/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs b/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs index 99952a17..ef0eeb5f 100644 --- a/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs +++ b/test/Qwiq.Core.Tests/WorkItemLinkTypeComparerTests.cs @@ -23,8 +23,8 @@ public class WorkItemLinkTypeComparerTests : ContextSpecification public override void Given() { - _instance = WorkItemLinkTypeComparer.Instance; - _instance2 = WorkItemLinkTypeEndComparer.Instance; + _instance = WorkItemLinkTypeComparer.Default; + _instance2 = WorkItemLinkTypeEndComparer.Default; _first = new MockWorkItemLinkType(CoreLinkTypeReferenceNames.Hierarchy); _second = new MockWorkItemLinkType(CoreLinkTypeReferenceNames.Hierarchy); diff --git a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs index e52df587..6c1ab894 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs +++ b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; +using Microsoft.Qwiq.Benchmark; using Microsoft.Qwiq.Identity.Mapper; using Microsoft.Qwiq.Mapper; using Microsoft.Qwiq.Mapper.Attributes; @@ -11,7 +12,6 @@ using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Qwiq.Benchmark; using Qwiq.Identity.Tests.Mocks; using B = Microsoft.Qwiq.Identity.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_a_BulkIdentityAwareAttributeMapperStrategy; @@ -40,7 +40,7 @@ public void Execute_Identity_Mapping_Performance_Benchmark() public class Benchmark { private IWorkItemMapperStrategy _strategy; - private IEnumerable> _workItemMappings; + private IEnumerable>> _workItemMappings; [Setup] public void SetupData() @@ -54,21 +54,19 @@ public void SetupData() var wis = new MockWorkItemStore(); var generator = new WorkItemGenerator(() => wis.Create(), new[] { "Revisions", "Item" }); wis.Add(generator.Generate()); - - _workItemMappings = generator.Items.Select(t => new KeyValuePair(t, new MockIdentityType())).ToList(); + + _workItemMappings = generator.Items.Select(t => new KeyValuePair>(t, new MockIdentityType())).ToList(); } [Benchmark] - public IEnumerable> Execute() + public IEnumerable>> Execute() { _strategy.Map(typeof(MockIdentityType), _workItemMappings, null); return _workItemMappings; } } } - - } namespace Microsoft.Qwiq.Mapper.Tests diff --git a/test/Qwiq.Identity.Benchmark.Tests/GlobalSuppressions.cs b/test/Qwiq.Identity.Benchmark.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..988edc1c257371e30c60c141ed35b45ecd49dc35 GIT binary patch literal 2380 zcmeHJO^eh(6s)u0e`s=2aI@37;)G8;x;+l2IE}sBN@F#%GL|e9sxP z;!J95TSjUoL5~}L8qNy+a(D`*zym}a5lUl-H9j*}jJHJDbG%|ygh%8NF~Yf54@L{G3KPEQ6oUT`d7=7MiKe&@!rF(w(;sX~u^$Vtc!&n@SB{2=zX zAub`$+7QFMs@)U2~{I4Gf$Iix#7LHQ;{=W(;eKc zTrC^1pepGdJ-(Qm)demdw`kmrhi;TVl!4wJ#mWUqfca z`6}f5HNJ3GjvG%hygQgVF^86}c0_59c$TIvF(`;Dbak6EHd^CYr7BjXqwCfd6PRwe zQn3}|OE1FLxox;R9Zr}>r<3va&>HQW=c6w7zA1%iSHkSbCfw>v z8uRzLZd%<&%6dcYUT=Y2G*jGhPk0XsJ3Qq&vWR#$ir=w%y(%1DF{>(%COhizfoyuN p%56ub59zjI+*GMyrC_3!3@2`qEMicrosoft.Qwiq.Identity.Benchmark.Tests v4.6 512 - - + true + ..\ + true + prompt + 4 + + ..\..\build\rulesets\noship.ruleset + true + true + 1591 true @@ -22,20 +30,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true @@ -124,6 +125,7 @@ + diff --git a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs index 6c86f7af..74fe3f07 100644 --- a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs +++ b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs @@ -19,7 +19,7 @@ namespace Qwiq.Identity.Tests public abstract class BulkIdentityAwareAttributeMapperStrategyTests : ContextSpecification { private IWorkItemMapperStrategy _strategy; - private IEnumerable> _workItemMappings; + private IEnumerable>> _workItemMappings; protected IDictionary> Identities { get; set; } protected MockIdentityType Actual @@ -47,7 +47,7 @@ public override void Given() }) }; - _workItemMappings = sourceWorkItems.Select(t => new KeyValuePair(t, new MockIdentityType())).ToList(); + _workItemMappings = sourceWorkItems.Select(t => new KeyValuePair>(t, new MockIdentityType())).ToList(); } public override void When() diff --git a/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs b/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs index 5ddc82fa..2e29d057 100644 --- a/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs +++ b/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs @@ -49,7 +49,7 @@ public class Given_an_alias_which_can_be_resolved_by_identity_descriptor : Singu { public override void Given() { - ExpectedIdentity = MockIdentityManagementService.Chrisj; + ExpectedIdentity = Identities.Chrisj; Alias = ExpectedIdentity.GetUserAlias(); base.Given(); } @@ -60,7 +60,7 @@ public class Given_an_alias_which_can_be_resolved_by_searching : SingularIdentit { public override void Given() { - ExpectedIdentity = MockIdentityManagementService.Chrisjoh; + ExpectedIdentity = Identities.Chrisjoh; Alias = KnownSearchAliasForChrisjoh; base.Given(); } @@ -100,8 +100,8 @@ public class Given_aliases_which_can_be_resolved_by_identity_descriptor : MultiI { public override void Given() { - var contestant1 = MockIdentityManagementService.Chrisj; - var contestant2 = MockIdentityManagementService.Danj; + var contestant1 = Identities.Chrisj; + var contestant2 = Identities.Danj; ExpectedIdentities = new Dictionary { { contestant1.GetUserAlias(), contestant1 }, @@ -119,8 +119,8 @@ public override void Given() { ExpectedIdentities = new Dictionary { - { KnownSearchAliasForChrisjoh, MockIdentityManagementService.Chrisjoh }, - { KnownSearchAliasForDanj, MockIdentityManagementService.Danj } + { KnownSearchAliasForChrisjoh, Identities.Chrisjoh }, + { KnownSearchAliasForDanj, Identities.Danj } }; Aliases = ExpectedIdentities.Keys.ToArray(); base.Given(); @@ -147,11 +147,11 @@ public class Given_aliases_which_can_be_resolved_by_different_methods : MultiIde { public override void Given() { - var danj = MockIdentityManagementService.Chrisj; + var danj = Identities.Chrisj; ExpectedIdentities = new Dictionary { { danj.GetUserAlias(), danj }, - { KnownSearchAliasForChrisjoh, MockIdentityManagementService.Chrisjoh }, + { KnownSearchAliasForChrisjoh, Identities.Chrisjoh }, { UnknownAliasB, null } }; Aliases = ExpectedIdentities.Keys.ToArray(); diff --git a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs index d69f6aed..40608f7f 100644 --- a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs +++ b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs @@ -1,12 +1,12 @@ using System; +using Microsoft.Qwiq; using Microsoft.Qwiq.Identity.Attributes; -using Microsoft.Qwiq.Mapper; using Microsoft.Qwiq.Mapper.Attributes; namespace Qwiq.Identity.Tests.Mocks { - public class MockIdentityType : IIdentifiable + public class MockIdentityType : IIdentifiable { internal const string BackingField = "Identity WorkItemField"; diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index d51d8293..e6935015 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Soap; -using Microsoft.VisualStudio.Services.Client; -using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Integration.Tests { @@ -12,40 +9,24 @@ public static class IntegrationSettings { public static readonly Func CreateRestStore = () => { - var options = IntegrationOptions(); - options.ClientType = ClientType.Rest; + var options = + new AuthenticationOptions( + Uri, + AuthenticationTypes.Windows, + ClientType.Rest); return WorkItemStoreFactory.Instance.Create(options); }; public static readonly Func CreateSoapStore = () => { - var options = IntegrationOptions(); + var options = + new AuthenticationOptions( + Uri, + AuthenticationTypes.Windows, + ClientType.Soap); return WorkItemStoreFactory.Instance.Create(options); }; - private static readonly Func IntegrationOptions = Options; - - private static IEnumerable CreateCredentials(AuthenticationType t) - { - // User did not specify a username or a password, so use the process identity - yield return new VssClientCredentials(new WindowsCredential(false)) - { - Storage = new VssClientCredentialStorage(), - PromptType = CredentialPromptType.DoNotPrompt - }; - - // Use the Windows identity of the logged on user - yield return new VssClientCredentials(true) - { - Storage = new VssClientCredentialStorage(), - PromptType = CredentialPromptType.PromptIfNeeded - }; - } - - private static AuthenticationOptions Options() - { - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - return new AuthenticationOptions(uri, AuthenticationType.Windows) { CreateCredentials = CreateCredentials }; - } + private static readonly Uri Uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/ProjectTests.cs b/test/Qwiq.Integration.Tests/ProjectTests.cs index 88490bd2..04eed63f 100644 --- a/test/Qwiq.Integration.Tests/ProjectTests.cs +++ b/test/Qwiq.Integration.Tests/ProjectTests.cs @@ -17,7 +17,7 @@ public class Given_a_Project_from_each_WorkItemStore_implementation : ProjectCom [TestCategory("REST")] public void Each_project_equals_eachother() { - RestProject.ShouldEqual(SoapProject, ProjectComparer.Instance); + RestProject.ShouldEqual(SoapProject, ProjectComparer.Default); RestProject.GetHashCode().ShouldEqual(SoapProject.GetHashCode()); } @@ -27,8 +27,8 @@ public void Each_project_equals_eachother() [TestCategory("REST")] public void Each_project_contains_the_same_Area_paths() { - RestProject.AreaRootNodes.ShouldContainOnly(SoapProject.AreaRootNodes, NodeComparer.Instance); - RestProject.AreaRootNodes.ShouldEqual(SoapProject.AreaRootNodes, Comparer.NodeCollectionComparer); + RestProject.AreaRootNodes.ShouldContainOnly(SoapProject.AreaRootNodes, NodeComparer.Default); + RestProject.AreaRootNodes.ShouldEqual(SoapProject.AreaRootNodes, Comparer.NodeCollection); RestProject.AreaRootNodes.GetHashCode().ShouldEqual(SoapProject.AreaRootNodes.GetHashCode()); } @@ -38,8 +38,8 @@ public void Each_project_contains_the_same_Area_paths() [TestCategory("REST")] public void Each_project_contains_the_same_Iteration_paths() { - RestProject.IterationRootNodes.ShouldContainOnly(SoapProject.IterationRootNodes, NodeComparer.Instance); - RestProject.IterationRootNodes.ShouldEqual(SoapProject.IterationRootNodes, Comparer.NodeCollectionComparer); + RestProject.IterationRootNodes.ShouldContainOnly(SoapProject.IterationRootNodes, NodeComparer.Default); + RestProject.IterationRootNodes.ShouldEqual(SoapProject.IterationRootNodes, Comparer.NodeCollection); RestProject.IterationRootNodes.GetHashCode().ShouldEqual(SoapProject.IterationRootNodes.GetHashCode()); } @@ -85,7 +85,7 @@ public void Each_project_contains_the_same_WorkItemTypes_with_the_same_FieldDefi .ShouldEqual(swfd.GetHashCode(), $"{rw.Name}:{rwfd.ReferenceName}:GetHashCode"); } - rw.FieldDefinitions.ShouldEqual(sw.FieldDefinitions, FieldDefinitionCollectionComparer.Instance); + rw.FieldDefinitions.ShouldEqual(sw.FieldDefinitions, Comparer.FieldDefinitionCollection); rw.FieldDefinitions.GetHashCode() .ShouldEqual( sw.FieldDefinitions.GetHashCode(), @@ -101,7 +101,7 @@ public void Each_project_contains_the_same_WorkItemTypes_with_the_same_FieldDefi { RestProject.WorkItemTypes.ShouldEqual( SoapProject.WorkItemTypes, - WorkItemTypeCollectionComparer.Instance); + WorkItemTypeCollectionComparer.Default); RestProject.WorkItemTypes.GetHashCode() .ShouldEqual( SoapProject.WorkItemTypes.GetHashCode(), diff --git a/test/Qwiq.Integration.Tests/RestWorkItemContextSpecification.cs b/test/Qwiq.Integration.Tests/RestWorkItemContextSpecification.cs index a435a5d7..52599e75 100644 --- a/test/Qwiq.Integration.Tests/RestWorkItemContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/RestWorkItemContextSpecification.cs @@ -2,14 +2,9 @@ namespace Microsoft.Qwiq.Integration.Tests { public abstract class RestWorkItemContextSpecification : WorkItemContextSpecification { - - protected override IWorkItemStore Create() { - var options = AuthenticationOptions; - options.ClientType = ClientType.Rest; - - return TimedAction(() => Rest.WorkItemStoreFactory.Instance.Create(options), "REST", "WIS Create"); + return TimedAction(() => IntegrationSettings.CreateRestStore(), "REST", "WIS Create"); } } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/SoapWorkItemContextSpecification.cs b/test/Qwiq.Integration.Tests/SoapWorkItemContextSpecification.cs index 73f87e7c..4e4227e8 100644 --- a/test/Qwiq.Integration.Tests/SoapWorkItemContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/SoapWorkItemContextSpecification.cs @@ -6,10 +6,7 @@ public abstract class SoapWorkItemContextSpecification : WorkItemContextSpecific protected override IWorkItemStore Create() { - var options = AuthenticationOptions; - options.ClientType = ClientType.Soap; - - return TimedAction(() => Soap.WorkItemStoreFactory.Instance.Create(options), "SOAP", "WIS Create"); + return TimedAction(() => IntegrationSettings.CreateSoapStore(), "SOAP", "WIS Create"); } } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs index 69897824..22461d8c 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs @@ -30,8 +30,8 @@ public abstract class WorkItemStoreComparisonContextSpecification : TimedContext public override void Cleanup() { - Rest?.Dispose(); - Soap?.Dispose(); + RestResult?.Dispose(); + SoapResult?.Dispose(); base.Cleanup(); } diff --git a/test/Qwiq.Integration.Tests/WorkItemTests.cs b/test/Qwiq.Integration.Tests/WorkItemTests.cs index 249326ab..dc703add 100644 --- a/test/Qwiq.Integration.Tests/WorkItemTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemTests.cs @@ -1,46 +1,31 @@ -using System; -using System.Collections.Generic; - -using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Rest; -using Microsoft.VisualStudio.Services.Client; -using Microsoft.VisualStudio.Services.Common; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; namespace Microsoft.Qwiq.Integration.Tests { + [TestClass] + public class Given_a_WorkItem_from_REST : RestWorkItemContextSpecification + { + } + + [TestClass] + public class Given_a_WorkItem_from_SOAP : RestWorkItemContextSpecification + { + } + public abstract class WorkItemContextSpecification : WorkItemStoreTests where T : IWorkItemStore { private const int Id = 10726528; - protected AuthenticationOptions AuthenticationOptions - { - get - { - var uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - - var options = new AuthenticationOptions(uri, AuthenticationType.Windows) { CreateCredentials = CreateCredentials }; - return options; - } - } - - private static IEnumerable CreateCredentials(AuthenticationType t) - { - // User did not specify a username or a password, so use the process identity - yield return new VssClientCredentials(new WindowsCredential(false)) { Storage = new VssClientCredentialStorage(), PromptType = CredentialPromptType.DoNotPrompt }; - - // Use the Windows identity of the logged on user - yield return new VssClientCredentials(true) { Storage = new VssClientCredentialStorage(), PromptType = CredentialPromptType.PromptIfNeeded }; - } - protected IWorkItem Result { get; private set; } - public override void When() + [TestMethod] + [TestCategory("localOnly")] + public void Reading_Id_from_Fields_property_with_ReferenceName_equals_the_property_value() { - Result = WorkItemStore.Query(Id); + Result.Fields[CoreFieldRefNames.Id]?.Value?.ToString().ShouldEqual(Result.Id.ToString()); } [TestMethod] @@ -50,23 +35,9 @@ public void Reading_Id_from_this_operator_with_ReferenceName_equals_the_property Result[CoreFieldRefNames.Id]?.ToString().ShouldEqual(Result.Id.ToString()); } - [TestMethod] - [TestCategory("localOnly")] - public void Reading_Id_from_Fields_property_with_ReferenceName_equals_the_property_value() + public override void When() { - Result.Fields[CoreFieldRefNames.Id]?.Value?.ToString().ShouldEqual(Result.Id.ToString()); + Result = WorkItemStore.Query(Id); } } - - [TestClass] - public class Given_a_WorkItem_from_REST : RestWorkItemContextSpecification - { - - } - - [TestClass] - public class Given_a_WorkItem_from_SOAP : RestWorkItemContextSpecification - { - - } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs b/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs index 1feaaec3..23adb6e8 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/MockModel.cs @@ -9,7 +9,7 @@ namespace Microsoft.Qwiq.Mapper.Benchmark.Tests { [WorkItemType("Task")] - public class MockModel : IIdentifiable + public class MockModel : IIdentifiable { public const string ForwardLinkName = "NS.SampleLink-Forward"; diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs index f27335de..c0ade959 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs @@ -5,13 +5,12 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; +using Microsoft.Qwiq.Benchmark; using Microsoft.Qwiq.Mapper.Attributes; using Microsoft.Qwiq.Mocks; using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Qwiq.Benchmark; - using B = Microsoft.Qwiq.Mapper.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy; namespace Microsoft.Qwiq.Mapper.Benchmark.Tests diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs index bf1ae36a..ae16f237 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs @@ -5,13 +5,12 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; +using Microsoft.Qwiq.Benchmark; using Microsoft.Qwiq.Mapper.Attributes; using Microsoft.Qwiq.Mocks; using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Qwiq.Benchmark; - using B = Microsoft.Qwiq.Mapper.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_Links_with_an_AttributeMapperStrategy_and_WorkItemLinksMapperStrategy; namespace Microsoft.Qwiq.Mapper.Benchmark.Tests diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 64bab87f..cb368a60 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Microsoft.Qwiq.Credentials; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Mapper.Tests.Mocks { @@ -30,7 +30,7 @@ public InstrumentedMockWorkItemStore(IWorkItemStore innerWorkItemStore) public int WorkItemLinkTypesCallCount { get; private set; } - public TfsCredentials AuthorizedCredentials => _innerWorkItemStore.AuthorizedCredentials; + public VssCredentials AuthorizedCredentials => _innerWorkItemStore.AuthorizedCredentials; public ClientType ClientType => _innerWorkItemStore.ClientType; @@ -47,7 +47,7 @@ public IProjectCollection Projects public IRegisteredLinkTypeCollection RegisteredLinkTypes => _innerWorkItemStore.RegisteredLinkTypes; - public ITfsTeamProjectCollection TeamProjectCollection + public ITeamProjectCollection TeamProjectCollection { get { diff --git a/test/Qwiq.Mapper.Tests/Mocks/MockModel.cs b/test/Qwiq.Mapper.Tests/Mocks/MockModel.cs index 459e9ac1..fe9fd9f1 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/MockModel.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/MockModel.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq.Mapper.Tests.Mocks { [WorkItemType("MockWorkItem")] - public class MockModel : IIdentifiable + public class MockModel : IIdentifiable { [FieldDefinition("Id")] public virtual int? Id { get; internal set; } diff --git a/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoBacking.cs b/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoBacking.cs index 08b5be61..18e62fb4 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoBacking.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoBacking.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq.Mapper.Tests.Mocks { [WorkItemType("Baz")] - public class MockModelWithNoBacking : IIdentifiable + public class MockModelWithNoBacking : IIdentifiable { [FieldDefinition("Id")] public virtual int? Id { get; internal set; } diff --git a/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoType.cs b/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoType.cs index 4d8f1f34..06061c64 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoType.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/MockModelWithNoType.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Mapper.Tests.Mocks { - public class MockModelWithNoType : IIdentifiable + public class MockModelWithNoType : IIdentifiable { [FieldDefinition("Id")] public virtual int? Id { get; internal set; } diff --git a/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs b/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs index fcadca7b..b66f0b20 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq.Mapper.Tests.Mocks { [WorkItemType("SimpleMockWorkItem")] - public class SimpleMockModel : IIdentifiable + public class SimpleMockModel : IIdentifiable { [FieldDefinition(CoreFieldRefNames.Id)] public int? Id { get; internal set; } diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 77f5ecf3..00c14e2c 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -45,16 +45,32 @@ true + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + ..\..\packages\Should.1.1.20\lib\Should.dll + + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 5de469cc..3dbf2a7e 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Qwiq.Mapper.Tests { public abstract class WorkItemMapperContext : ContextSpecification - where T : IIdentifiable, new() + where T : IIdentifiable, new() { protected readonly Dictionary WorkItemBackingStore = new Dictionary { @@ -79,7 +79,7 @@ public override void When() public override void Cleanup() { - WorkItemStore.Dispose(); + WorkItemStore?.Dispose(); } } @@ -100,7 +100,7 @@ public void the_mapped_property_is_the_substituted_value() } [WorkItemType("MissingFields")] - public class MockModelWithMissingField : IIdentifiable + public class MockModelWithMissingField : IIdentifiable { [FieldDefinition("Id")] public virtual int? Id { get; internal set; } @@ -116,7 +116,7 @@ public class when_an_issue_is_mapped_with_a_field_containing_null_that_does_not_ public override void Given() { SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("MissingFields", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; - WorkItemStore = new MockWorkItemStore(SourceWorkItems); + WorkItemStore = new MockWorkItemStore().Add(SourceWorkItems); base.Given(); } @@ -127,7 +127,7 @@ public void the_mapped_property_is_the_substituted_value() } [WorkItemType("Default")] - public class MockModelWithMissingField : IIdentifiable + public class MockModelWithMissingField : IIdentifiable { [FieldDefinition("Id")] public int? Id { get; internal set; } @@ -252,8 +252,7 @@ private static void PropertiesAreEqual(T expected, T actual) var expectedVal = property.GetValue(expected); var actualVal = property.GetValue(actual); - var val = expectedVal as ICollection; - if (val != null) + if (expectedVal is ICollection val) { CollectionAssert.AreEqual(val, (ICollection)actualVal); } diff --git a/test/Qwiq.Mapper.Tests/packages.config b/test/Qwiq.Mapper.Tests/packages.config index 7195e387..4739bc8d 100644 --- a/test/Qwiq.Mapper.Tests/packages.config +++ b/test/Qwiq.Mapper.Tests/packages.config @@ -1,8 +1,11 @@  + + + \ No newline at end of file diff --git a/test/Qwiq.Mocks/Extensions.cs b/test/Qwiq.Mocks/Extensions.cs index bfca699f..21c7c333 100644 --- a/test/Qwiq.Mocks/Extensions.cs +++ b/test/Qwiq.Mocks/Extensions.cs @@ -4,13 +4,20 @@ namespace Microsoft.Qwiq.Mocks { - public static class Extensions + + + public static partial class Extensions { internal static void BatchSave(this MockWorkItemStore store, params IWorkItem[] workItems) { store.BatchSave(workItems); } + public static MockWorkItem Create(this MockWorkItemStore store) + { + return Create(store, null); + } + public static MockWorkItem Create(this MockWorkItemStore store, IEnumerable> values = null) { var project = store.Projects[0]; @@ -22,11 +29,17 @@ public static MockWorkItem Create(this MockWorkItemStore store, IEnumerable(store.Create, new []{"Revisions", "Item"}); + return g.Generate(1).Single(); + } + public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable workItems) { return store.Add(workItems, null); diff --git a/test/Qwiq.Mocks/GlobalSuppressions.cs b/test/Qwiq.Mocks/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..e0176054f1cb2146c102b0de86277c97f4f89b34 GIT binary patch literal 7772 zcmeHMTWi!%5T0kj|8R&e3U+I?q6j{0TSdX%OjSgDNV91h-6l1cO8>n2eKUJZ_Ns+G zq!5;Dc26dA{bo)w^ZVBqiKN6Qk#jkgv0TXuaT4NON~Nm<*^v}fr;72(3|wl+AITf&af#KBs!xiQ5S%F`1J^yQp6fkI`D~(wQ;|f5%H>#@fj1#K29&&Bw5vsl>%D^x%#`JWwWcI8>>Z zh$Rs`kb9{0jlO+cmshAA_2#!*64gfAF2(&s9>dIAbkTAluP740+fI?s=N^j6i=Z9ESW*QKylbTRw7 zvQtibiqD7h&o|x5%xR6g7~4KK|2H>tOxzE&cJAHee~nf4@f~_9SNQf5C^t8Q za_G+FpkP;=ed20z=DXag*_6|%kmv?Lr%U0DN{9-?s6@h#r&?LIgdknGa zwCe@`a~2Sz)9dx{XU3m)u#-j^P{b$KjU@^?^Ln|IgAeH2^K$k%e3_q zHzJH)F~7FGvkGI3KXn`9zEY)LKzGBN*Djbt-0TyRWuW(PTi3=X0p?xQZx+3V3a7IX yS{}4K>f|%Dsd3jxJxY8K6B+2*NcEmx>}A8eA public static IFieldDefinition Create(string name) { - IFieldDefinition field; - if (CoreFieldDefinitions.NameLookup.TryGetValue(name, out field)) return field; + if (CoreFieldDefinitions.NameLookup.TryGetValue(name, out IFieldDefinition field)) return field; if (CoreFieldDefinitions.ReferenceNameLookup.TryGetValue(name, out field)) return field; diff --git a/test/Qwiq.Mocks/MockIdentityManagementService.cs b/test/Qwiq.Mocks/MockIdentityManagementService.cs index 8277099f..bb8dca51 100644 --- a/test/Qwiq.Mocks/MockIdentityManagementService.cs +++ b/test/Qwiq.Mocks/MockIdentityManagementService.cs @@ -5,38 +5,68 @@ namespace Microsoft.Qwiq.Mocks { - public class MockIdentityManagementService : IIdentityManagementService + public static class Identities { public static readonly ITeamFoundationIdentity Danj = new MockTeamFoundationIdentity( - new MockIdentityDescriptor("danj", "contoso.com"), - "Dan Jump", - Guid.Parse("b7de08a6-8417-491b-be62-85945a538f46")); + new MockIdentityDescriptor("danj", "contoso.com"), + "Dan Jump", + Guid.Parse("b7de08a6-8417-491b-be62-85945a538f46")); public static readonly ITeamFoundationIdentity Adamb = new MockTeamFoundationIdentity( - new MockIdentityDescriptor("adamb", "contoso.com"), - "Adam Barr", - Guid.Parse("7846c22f-d3d8-4e02-8b62-d055d0284783")); + new MockIdentityDescriptor("adamb", "contoso.com"), + "Adam Barr", + Guid.Parse("7846c22f-d3d8-4e02-8b62-d055d0284783")); public static readonly ITeamFoundationIdentity Chrisj = new MockTeamFoundationIdentity( - new MockIdentityDescriptor("chrisj", "contoso.com"), - "Chris Johnson", - Guid.Parse("f92c1baa-0038-4247-be68-12043fcc34e3"), - false); + new MockIdentityDescriptor("chrisj", "contoso.com"), + "Chris Johnson", + Guid.Parse("f92c1baa-0038-4247-be68-12043fcc34e3"), + false); public static readonly ITeamFoundationIdentity Chrisjoh = new MockTeamFoundationIdentity( - new MockIdentityDescriptor("chrisjoh", "contoso.com"), - "Chris Johnson (FINANCE)", - Guid.Parse("41e97533-89f7-45d7-8246-eaa449b5651d")); + new MockIdentityDescriptor("chrisjoh", "contoso.com"), + "Chris Johnson (FINANCE)", + Guid.Parse("41e97533-89f7-45d7-8246-eaa449b5651d")); public static readonly ITeamFoundationIdentity Chrisjohn = new MockTeamFoundationIdentity( - new MockIdentityDescriptor("chrisjohn", "contoso.com"), - "Chris F. Johnson", - Guid.Parse("b3da460c-6191-4725-b08d-52bba48a574f")); + new MockIdentityDescriptor("chrisjohn", "contoso.com"), + "Chris F. Johnson", + Guid.Parse("b3da460c-6191-4725-b08d-52bba48a574f")); public static readonly ITeamFoundationIdentity Chrisjohns = new MockTeamFoundationIdentity( - new MockIdentityDescriptor("chrisjohns", "contoso.com"), - "Chris Johnson ", - Guid.Parse("67b42b6c-6bd8-40e2-a622-fe69eacd3d47")); + new MockIdentityDescriptor("chrisjohns", "contoso.com"), + "Chris Johnson ", + Guid.Parse("67b42b6c-6bd8-40e2-a622-fe69eacd3d47")); + + public static readonly ITeamFoundationIdentity[] All = { + Danj, + Adamb, + Chrisj, + Chrisjoh, + Chrisjohn, + Chrisjohns + }; + } + + public class MockIdentityManagementService : IIdentityManagementService + { + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Danj instead.")] + public static readonly ITeamFoundationIdentity Danj =Identities.Danj; + + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Adamb instead.")] + public static readonly ITeamFoundationIdentity Adamb = Identities.Adamb; + + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Chrisj instead.")] + public static readonly ITeamFoundationIdentity Chrisj = Identities.Chrisj; + + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Chrisjoh instead.")] + public static readonly ITeamFoundationIdentity Chrisjoh = Identities.Chrisjoh; + + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Chrisjohn instead.")] + public static readonly ITeamFoundationIdentity Chrisjohn = Identities.Chrisjohn; + + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Chrisjohns instead.")] + public static readonly ITeamFoundationIdentity Chrisjohns = Identities.Chrisjohns; private readonly IDictionary _accountNameMappings; @@ -46,15 +76,7 @@ public class MockIdentityManagementService : IIdentityManagementService /// Initializes a new instance of the IMS with Contoso users (danj, adamb, chrisj, chrisjoh, chrisjohn, chrisjohns) ///

public MockIdentityManagementService() - : this(new[] - { - Danj, - Adamb, - Chrisj, - Chrisjoh, - Chrisjohn, - Chrisjohns, - }) + : this(Identities.All) { } @@ -73,8 +95,10 @@ public MockIdentityManagementService(IEnumerable identi /// public MockIdentityManagementService(IDictionary accountNameMappings) { + if (accountNameMappings == null) throw new ArgumentNullException(nameof(accountNameMappings)); + _accountNameMappings = new Dictionary(StringComparer.OrdinalIgnoreCase); - _descriptorMappings = new Dictionary(IdentityDescriptorComparer.Instance); + _descriptorMappings = new Dictionary(IdentityDescriptorComparer.Default); foreach (var account in accountNameMappings) { @@ -107,7 +131,7 @@ public MockIdentityManagementService(IDictionary k.Key, e => e.Value.ToArray()) ?? new Dictionary(StringComparer.OrdinalIgnoreCase); - _descriptorMappings = new Dictionary(IdentityDescriptorComparer.Instance); + _descriptorMappings = new Dictionary(IdentityDescriptorComparer.Default); foreach (var accounts in _accountNameMappings.Values) { @@ -135,8 +159,7 @@ public IEnumerable ReadIdentities(ICollection Locate(Func a, (a, i) => new { a, i }) - .Where(@t => predicate(@t.i)) - .Select(@t => @t.i) + .Where(t => predicate(t.i)) + .Select(t => t.i) .ToArray(); } } diff --git a/test/Qwiq.Mocks/MockProject.cs b/test/Qwiq.Mocks/MockProject.cs index 1062e9a2..e9f48f09 100644 --- a/test/Qwiq.Mocks/MockProject.cs +++ b/test/Qwiq.Mocks/MockProject.cs @@ -32,24 +32,24 @@ internal MockProject(IWorkItemStore store, INode node) private static INodeCollection CreateNodes(bool area) { var root = new Node(1, area, !area, "Root", new Uri("http://localhost/nodes/1")); - var l1 = new Node( - 2, - area, - !area, - "L1", - new Uri("http://localhost/nodes/2"), - () => root, - n => new[] - { - new Node( - 3, - area, - !area, - "L2", - new Uri("http://localhost/nodes/3"), - () => n, - c => Enumerable.Empty()) - }); + new Node( + 2, + area, + !area, + "L1", + new Uri("http://localhost/nodes/2"), + () => root, + n => new[] + { + new Node( + 3, + area, + !area, + "L2", + new Uri("http://localhost/nodes/3"), + () => n, + c => Enumerable.Empty()) + }); return new NodeCollection(root); } diff --git a/test/Qwiq.Mocks/MockQueryFactory.cs b/test/Qwiq.Mocks/MockQueryFactory.cs index 91324b48..983fcf62 100644 --- a/test/Qwiq.Mocks/MockQueryFactory.cs +++ b/test/Qwiq.Mocks/MockQueryFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace Microsoft.Qwiq.Mocks { @@ -7,24 +8,25 @@ public class MockQueryFactory : IQueryFactory { private readonly MockWorkItemStore _store; - private readonly List _wiqls = new List(); + private IList _queries; public MockQueryFactory(MockWorkItemStore store) { _store = store ?? throw new ArgumentNullException(nameof(store)); + _queries = new List(); } public IQuery Create(string wiql, bool dayPrecision) { CreateCallCount++; - _wiqls.Add(wiql); + _queries.Add(wiql); return new MockQueryByWiql(wiql, _store); } public IQuery Create(IEnumerable ids, string wiql) { CreateCallCount++; - _wiqls.Add(wiql); + _queries.Add(wiql); return new MockQueryByWiql(ids, wiql, _store); } @@ -32,7 +34,9 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) { // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). // It is used for ASOF - var wiql = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; + FormattableString ws = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; + var wiql = ws.ToString(CultureInfo.InvariantCulture); + if (asOf.HasValue) { // If specified DateTime is not UTC convert it to local time based on TFS client TimeZone @@ -40,7 +44,8 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) asOf = DateTime.SpecifyKind( asOf.Value - _store.TimeZone.GetUtcOffset(asOf.Value), DateTimeKind.Utc); - wiql += $" ASOF \'{asOf.Value:u}\'"; + FormattableString ao = $" ASOF \'{asOf.Value:u}\'"; + wiql += ao.ToString(CultureInfo.InvariantCulture); } return Create(ids, wiql); @@ -48,6 +53,6 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) public int CreateCallCount { get; private set; } - public IEnumerable QueryWiqls => _wiqls; + public IEnumerable Queries => _queries; } } diff --git a/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs b/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs index 4f4d5003..3ff4e48a 100644 --- a/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs +++ b/test/Qwiq.Mocks/MockTfsTeamProjectCollection.cs @@ -1,10 +1,10 @@ using System; -using Microsoft.Qwiq.Credentials; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Mocks { - public class MockTfsTeamProjectCollection : ITfsTeamProjectCollection + public class MockTfsTeamProjectCollection : ITeamProjectCollection { public MockTfsTeamProjectCollection() : this(new MockIdentityManagementService()) @@ -17,7 +17,7 @@ public MockTfsTeamProjectCollection(IIdentityManagementService identityManagemen TimeZone = TimeZone.CurrentTimeZone; } - public TfsCredentials AuthorizedCredentials { get; set; } + public VssCredentials AuthorizedCredentials { get; set; } public ITeamFoundationIdentity AuthorizedIdentity { get; set; } diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index 348d0a2b..c1bbdeaf 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -106,7 +106,7 @@ public override IRelatedLink CreateRelatedLink(int id, IWorkItemLinkTypeEnd link } if (id == 0 && linkTypeEnd == null) return new MockRelatedLink(null, Id); - + return new MockRelatedLink(linkTypeEnd, Id, id); } @@ -185,8 +185,10 @@ public override IWorkItem Copy() } else { - var link = newItem.CreateRelatedLink(this); - newItem.Links.Add(link); + using (var wis = new MockWorkItemStore()) + { + newItem.Links.Add(newItem.CreateRelatedLink(wis.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related].ForwardEnd, this)); + } } newItem.Id = 0; diff --git a/test/Qwiq.Mocks/MockWorkItemLinkType.cs b/test/Qwiq.Mocks/MockWorkItemLinkType.cs index 80dd2d30..97319afa 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkType.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkType.cs @@ -49,8 +49,8 @@ public MockWorkItemLinkType(string referenceName) "Reference name not supported in mock object."); } - _forward = new MockWorkItemLinkTypeEnd(this, forwardName, true, -forwardId); - _reverse = new MockWorkItemLinkTypeEnd(this, reverseName, false, -reverseId); + SetForwardEnd(new MockWorkItemLinkTypeEnd(this, forwardName, true, -forwardId)); + SetReverseEnd(new MockWorkItemLinkTypeEnd(this, reverseName, false, -reverseId)); } public MockWorkItemLinkType( @@ -64,8 +64,8 @@ public MockWorkItemLinkType( if (string.IsNullOrEmpty(forwardEndName)) throw new ArgumentException("Value cannot be null or empty.", nameof(forwardEndName)); if (string.IsNullOrEmpty(reverseEndName)) throw new ArgumentException("Value cannot be null or empty.", nameof(reverseEndName)); IsDirectional = isDirectional; - _forward = new MockWorkItemLinkTypeEnd(this, forwardEndName, true, id); - _reverse = new MockWorkItemLinkTypeEnd(this, reverseEndName, false, -id); + SetForwardEnd(new MockWorkItemLinkTypeEnd(this, forwardEndName, true, id)); + SetReverseEnd(new MockWorkItemLinkTypeEnd(this, reverseEndName, false, -id)); } diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index e26c5307..d5913198 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.Linq; -using Microsoft.Qwiq.Credentials; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Mocks { @@ -19,9 +19,9 @@ public class MockWorkItemStore : IWorkItemStore private Lazy _projects; - private Lazy _tfs; + private readonly Lazy _tfs; - private Lazy _queryFactory; + private readonly Lazy _queryFactory; public MockWorkItemStore() : this(() => new MockTfsTeamProjectCollection(), store => new MockQueryFactory(store)) @@ -37,14 +37,14 @@ public MockWorkItemStore(IEnumerable workItems, IEnumerable tpcFactory, + Func tpcFactory, Func queryFactory ) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); - _tfs = new Lazy(tpcFactory); + _tfs = new Lazy(tpcFactory); _queryFactory = new Lazy(() => queryFactory(this)); _projects = new Lazy(() => new MockProjectCollection(this)); @@ -58,7 +58,7 @@ Func queryFactory private int WaitTime => Instance.Next(0, 3000); - public TfsCredentials AuthorizedCredentials => null; + public VssCredentials AuthorizedCredentials => null; public ClientType ClientType => ClientType.None; @@ -66,7 +66,7 @@ Func queryFactory public IProjectCollection Projects => _projects.Value; - public ITfsTeamProjectCollection TeamProjectCollection => _tfs.Value; + public ITeamProjectCollection TeamProjectCollection => _tfs.Value; public TimeZone TimeZone => _tfs.Value.TimeZone; @@ -151,7 +151,7 @@ internal void BatchSave(IEnumerable workItems) if (!project.WorkItemTypes.Contains(witName)) { Trace.TraceWarning("Project {0} is missing work item type definition {1}", project, witName); - missingWits.TryAdd(project, new HashSet(WorkItemTypeComparer.Instance)); + missingWits.TryAdd(project, new HashSet(WorkItemTypeComparer.Default)); var t = item.Type as MockWorkItemType; if (t?.Store != this) t.Store = this; @@ -225,7 +225,7 @@ private void Save(IWorkItem item) item[CoreFieldRefNames.RelatedLinkCount] = l[BaseLinkType.RelatedLink]; item[CoreFieldRefNames.ExternalLinkCount] = l[BaseLinkType.ExternalLink]; - item[CoreFieldRefNames.HyperLinkCount] = l[BaseLinkType.Hyperlink]; + item[CoreFieldRefNames.HyperlinkCount] = l[BaseLinkType.Hyperlink]; // Fix up Team Project if needed var projectName = item[CoreFieldRefNames.TeamProject]?.ToString(); @@ -257,11 +257,10 @@ private void SaveLink(ILink link, int id) var rl = link as IRelatedLink; if (rl == null) return; - var mrl = rl as MockRelatedLink; - if (mrl != null) + if (rl is MockRelatedLink mrl) { var li = mrl.LinkInfo; - if (LinkInfo.Contains(li, WorkItemLinkInfoComparer.Instance)) + if (LinkInfo.Contains(li, WorkItemLinkInfoComparer.Default)) { Trace.TraceWarning( $"Warning: Duplicate link. (Type: {li.LinkType?.ImmutableName ?? "NULL"}; Source: {li.SourceId}; Target: {li.TargetId})"); diff --git a/test/Qwiq.Mocks/MockWorkItemType.cs b/test/Qwiq.Mocks/MockWorkItemType.cs index 8c5d344c..a47c45ae 100644 --- a/test/Qwiq.Mocks/MockWorkItemType.cs +++ b/test/Qwiq.Mocks/MockWorkItemType.cs @@ -66,4 +66,6 @@ public MockWorkItemType( public IWorkItemStore Store { get; set; } } + + } \ No newline at end of file diff --git a/test/Qwiq.Mocks/Properties/AssemblyInfo.cs b/test/Qwiq.Mocks/Properties/AssemblyInfo.cs index 55212d27..fa0e768a 100644 --- a/test/Qwiq.Mocks/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Mocks/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ +using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -19,6 +19,8 @@ // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] +[assembly: CLSCompliant(true)] + // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("db07e690-4b77-414f-91c7-1a48c9f01f24")] diff --git a/test/Qwiq.Benchmark/WorkItemGenerator.cs b/test/Qwiq.Mocks/PropertyValueGenerator.cs similarity index 55% rename from test/Qwiq.Benchmark/WorkItemGenerator.cs rename to test/Qwiq.Mocks/PropertyValueGenerator.cs index 90c88f7c..ae224acd 100644 --- a/test/Qwiq.Benchmark/WorkItemGenerator.cs +++ b/test/Qwiq.Mocks/PropertyValueGenerator.cs @@ -1,26 +1,35 @@ -using Microsoft.Qwiq; -using Microsoft.Qwiq.Mocks; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; -namespace Qwiq.Benchmark +namespace Microsoft.Qwiq.Mocks { - public class WorkItemGenerator + public class PropertyValueGenerator where T : IWorkItem { + private readonly string[] _assignees; private readonly HashSet _propertiesToSkip; + protected const string Chars = "$%#@!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&"; - private readonly Func _create; - - private string[] _assignees - ; + public PropertyValueGenerator() + :this(null) + { + } - public WorkItemGenerator(Func createFunc, IEnumerable propertiesToSkip = null) + public PropertyValueGenerator(IEnumerable propertiesToSkip) { - _create = createFunc ?? throw new ArgumentNullException(nameof(createFunc)); + _assignees = new[] + { + Identities.Danj.DisplayName, + Identities.Adamb.DisplayName, + Identities.Chrisj.DisplayName, + Identities.Chrisjoh.DisplayName, + Identities.Chrisjohn.DisplayName, + Identities.Chrisjohns.DisplayName + }; + _propertiesToSkip = propertiesToSkip == null ? new HashSet(StringComparer.OrdinalIgnoreCase) : new HashSet(propertiesToSkip, StringComparer.OrdinalIgnoreCase); @@ -46,108 +55,13 @@ public WorkItemGenerator(Func createFunc, IEnumerable propertiesToSki _propertiesToSkip.Add("RevisedDate"); _propertiesToSkip.Add("History"); _propertiesToSkip.Add("Watermark"); - - //// Identity fields - //_propertiesToSkip.Add("AssignedTo"); - //_propertiesToSkip.Add("ChangedBy"); - //_propertiesToSkip.Add("CreatedBy"); - - _assignees = new[] - { - MockIdentityManagementService.Danj.DisplayName, - MockIdentityManagementService.Adamb.DisplayName, - MockIdentityManagementService.Chrisj.DisplayName, - MockIdentityManagementService.Chrisjoh.DisplayName, - MockIdentityManagementService.Chrisjohn.DisplayName, - MockIdentityManagementService.Chrisjohns.DisplayName - }; } - public IReadOnlyCollection Generate(int quantity = 50) - { - // After generating the parent/child links, this can grow an order of magnitude - var items = new List(quantity * 10); - var generatedItems = new HashSet(); - - int GenerateUnusedWorkItemId() - { - // ID needs to be populated prior to other properties (as they may depend on that value) - var id = Randomizer.Instance.NextSystemId(quantity); - while (generatedItems.Contains(id)) - { - id = Randomizer.Instance.NextSystemId(quantity * 10); - } - - return id; - } - - for (var i = 0; i < quantity; i++) - { - GenerateItem(_create, GenerateUnusedWorkItemId, generatedItems, items); - } - - Items = items.AsReadOnly(); - return Items; - } - - // Generates an item and link references - private T GenerateItem(Func createFunc, Func idFunc, ISet generatedItems, ICollection items) - { - var instance = GenerateItem(createFunc, idFunc); - - if (generatedItems.Contains(instance.Id)) - { - // Item has already been generated - var id = instance.Id; - return items.Single(p => p.Id == id); - } - - - items.Add(instance); - generatedItems.Add(instance.Id); - - foreach (var link in instance.Links.OfType().ToArray()) - { - var linked = default(T); - - if (!generatedItems.Contains(link.RelatedWorkItemId)) - { - linked = GenerateItem(createFunc, () => link.RelatedWorkItemId, generatedItems, items); - } - - // Determine if we need to create a recipricol link - if (!(link.LinkTypeEnd?.LinkType.IsDirectional ?? false)) continue; - - // Look up the item if it was not previously generated - if (linked == null) - { - linked = items.Single(p => p.Id == link.RelatedWorkItemId); - } - - // Add the recipricol link - linked.Links.Add(linked.CreateRelatedLink(instance.Id, link.LinkTypeEnd.OppositeEnd)); - } - - return instance; - } - - /// Generates a single item - private T GenerateItem(Func createFunc, Func idFunc) - { - var instance = createFunc(); - var id = idFunc(); - instance[CoreFieldRefNames.Id] = id; - PopulatePropertiesOnInstance(instance); - return instance; - } - - public IReadOnlyCollection Items { get; private set; } - - private void PopulatePropertiesOnInstance(T instance) + public virtual T PopulateInstance(T instance) { foreach ( var property in - typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)) + typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)) { // If we can't set the property, don't bother if (property.GetSetMethod() == null) continue; @@ -171,13 +85,12 @@ var property in // May fail because the setter is not available } } - } - protected const string Chars = "$%#@!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&"; + return instance; + } - protected virtual object GetRandomValue(T instance, string propertyName, Type propertyType) + public virtual object GetRandomValue(T instance, string propertyName, Type propertyType) { - var randomizer = Randomizer.Instance; object value; @@ -239,12 +152,12 @@ protected virtual object GetRandomValue(T instance, string propertyName, Type pr case "System.Uri": var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ/"; value = - new Uri( - "http://tempuri.org/" - + new string( - Enumerable.Repeat(c, randomizer.Next(5, 250)) - .Select(s => s[randomizer.Next(s.Length)]) - .ToArray())); + new Uri( + "http://tempuri.org/" + + new string( + Enumerable.Repeat(c, randomizer.Next(5, 250)) + .Select(s => s[randomizer.Next(s.Length)]) + .ToArray())); break; case "Microsoft.Qwiq.IWorkItemType": diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 81b08ef3..68745010 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -12,8 +12,18 @@ Microsoft.Qwiq.Mocks v4.6 512 - + true + ..\ + true + prompt + 4 + + ..\..\build\rulesets\ship.ruleset + true + true + 1591 + true @@ -21,20 +31,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true @@ -60,6 +63,7 @@ + @@ -79,6 +83,11 @@ + + + + + diff --git a/test/Qwiq.Benchmark/Randomizer.cs b/test/Qwiq.Mocks/Random.Extensions.cs similarity index 58% rename from test/Qwiq.Benchmark/Randomizer.cs rename to test/Qwiq.Mocks/Random.Extensions.cs index ac9cee3d..04a3c523 100644 --- a/test/Qwiq.Benchmark/Randomizer.cs +++ b/test/Qwiq.Mocks/Random.Extensions.cs @@ -1,29 +1,20 @@ using System; -namespace Qwiq.Benchmark +namespace Microsoft.Qwiq.Mocks { - public class Randomizer : Random + public static partial class Extensions { - private static Randomizer random; - - public static Randomizer Instance => random ?? (random = new Randomizer()); - - - } - - public static class RandomizerExtensions - { - public static int NextSystemId(this Randomizer instance) + public static int NextSystemId(this Random instance) { return instance.NextSystemId(int.MaxValue); } - public static int NextSystemId(this Randomizer instance, int max) + public static int NextSystemId(this Random instance, int max) { return instance.NextSystemId(1, max); } - public static int NextSystemId(this Randomizer instance, int min, int max) + public static int NextSystemId(this Random instance, int min, int max) { return instance.Next(min, max); } @@ -31,14 +22,14 @@ public static int NextSystemId(this Randomizer instance, int min, int max) /// /// Taken from http://stackoverflow.com/questions/609501/generating-a-random-decimal-in-c-sharp Jon Skeet's answer /// - public static decimal NextDecimal(this Randomizer instance) + public static decimal NextDecimal(this Random instance) { var scale = (byte)instance.Next(29); var sign = instance.Next(2) == 1; return new decimal(instance.NextInt32(), instance.NextInt32(), instance.NextInt32(), sign, scale); } - public static int NextInt32(this Randomizer instance) + public static int NextInt32(this Random instance) { unchecked { @@ -48,7 +39,7 @@ public static int NextInt32(this Randomizer instance) } } - public static bool ShouldEnter(this Randomizer instance) + public static bool ShouldEnter(this Random instance) { return instance.NextDouble() < 0.5; } diff --git a/test/Qwiq.Mocks/Randomizer.cs b/test/Qwiq.Mocks/Randomizer.cs new file mode 100644 index 00000000..fcb68460 --- /dev/null +++ b/test/Qwiq.Mocks/Randomizer.cs @@ -0,0 +1,11 @@ +using System; + +namespace Microsoft.Qwiq.Mocks +{ + public class Randomizer : Random + { + private static Randomizer random; + + public static Randomizer Instance => random ?? (random = new Randomizer()); + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/WorkItemGenerator.cs b/test/Qwiq.Mocks/WorkItemGenerator.cs new file mode 100644 index 00000000..a83f1c16 --- /dev/null +++ b/test/Qwiq.Mocks/WorkItemGenerator.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq.Mocks +{ + public class WorkItemGenerator + where T : IWorkItem + { + private readonly Func _create; + + private readonly PropertyValueGenerator _propertyGenerator; + + public WorkItemGenerator(Func createFunc) + : this(createFunc, null) + { + } + + public WorkItemGenerator(Func createFunc, IEnumerable propertiesToSkip = null) + { + _create = createFunc ?? throw new ArgumentNullException(nameof(createFunc)); + _propertyGenerator = new PropertyValueGenerator(propertiesToSkip); + } + + public System.Collections.Generic.IReadOnlyCollection Generate(int quantity = 50) + { + if (quantity <= 0) throw new ArgumentOutOfRangeException(nameof(quantity)); + // After generating the parent/child links, this can grow an order of magnitude + var items = new List(quantity * 10); + var generatedItems = new HashSet(); + + int GenerateUnusedWorkItemId() + { + // ID needs to be populated prior to other properties (as they may depend on that value) + var id = Randomizer.Instance.NextSystemId(quantity); + while (generatedItems.Contains(id)) + { + id = Randomizer.Instance.NextSystemId(quantity * 10); + } + + return id; + } + + for (var i = 0; i < quantity; i++) + { + GenerateItem(_create, GenerateUnusedWorkItemId, generatedItems, items); + } + + Items = items.AsReadOnly(); + return Items; + } + + // Generates an item and link references + private T GenerateItem(Func createFunc, Func idFunc, ISet generatedItems, ICollection items) + { + var instance = GenerateItem(createFunc, idFunc); + + if (generatedItems.Contains(instance.Id)) + { + // Item has already been generated + var id = instance.Id; + return items.Single(p => p.Id == id); + } + + + items.Add(instance); + generatedItems.Add(instance.Id); + + foreach (var link in instance.Links.OfType().ToArray()) + { + var linked = default(T); + + if (!generatedItems.Contains(link.RelatedWorkItemId)) + { + linked = GenerateItem(createFunc, () => link.RelatedWorkItemId, generatedItems, items); + } + + // Determine if we need to create a recipricol link + if (!(link.LinkTypeEnd?.LinkType.IsDirectional ?? false)) continue; + + // Look up the item if it was not previously generated + if (linked == null) + { + linked = items.Single(p => p.Id == link.RelatedWorkItemId); + } + + // Add the recipricol link + linked.Links.Add(linked.CreateRelatedLink(instance.Id, link.LinkTypeEnd.OppositeEnd)); + } + + return instance; + } + + /// Generates a single item + private T GenerateItem(Func createFunc, Func idFunc) + { + var instance = createFunc(); + var id = idFunc(); + instance[CoreFieldRefNames.Id] = id; + return _propertyGenerator.PopulateInstance(instance); + } + + public System.Collections.Generic.IReadOnlyCollection Items { get; private set; } + + protected const string Chars = "$%#@!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&"; + + protected virtual object GetRandomValue(T instance, string propertyName, Type propertyType) + { + return _propertyGenerator.GetRandomValue(instance, propertyName, propertyType); + + } + } +} diff --git a/test/Qwiq.Benchmark/WorkItemLinkGenerator.cs b/test/Qwiq.Mocks/WorkItemLinkGenerator.cs similarity index 90% rename from test/Qwiq.Benchmark/WorkItemLinkGenerator.cs rename to test/Qwiq.Mocks/WorkItemLinkGenerator.cs index 9f8d4b59..2d516549 100644 --- a/test/Qwiq.Benchmark/WorkItemLinkGenerator.cs +++ b/test/Qwiq.Mocks/WorkItemLinkGenerator.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using Microsoft.Qwiq; - -namespace Qwiq.Benchmark +namespace Microsoft.Qwiq.Mocks { public class WorkItemLinkGenerator : WorkItemGenerator where T : IWorkItem @@ -12,6 +10,14 @@ public class WorkItemLinkGenerator : WorkItemGenerator private readonly IWorkItemLinkType _linkType; + public WorkItemLinkGenerator( + Func createFunc, + IWorkItemLinkType linkType, + Func linkFunc) + : this(createFunc, linkType, linkFunc, null) + { + } + public WorkItemLinkGenerator( Func createFunc, IWorkItemLinkType linkType, From f77f9dba6c8bdd42625505fd1dd2cb75e4ecab2d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Apr 2017 17:40:15 -0700 Subject: [PATCH 110/251] Remove Debugger break when test fails --- .../Qwiq.Tests.Common/ContextSpecification.cs | 201 ++++++++---------- 1 file changed, 92 insertions(+), 109 deletions(-) diff --git a/test/Qwiq.Tests.Common/ContextSpecification.cs b/test/Qwiq.Tests.Common/ContextSpecification.cs index 66606ad4..a9343780 100644 --- a/test/Qwiq.Tests.Common/ContextSpecification.cs +++ b/test/Qwiq.Tests.Common/ContextSpecification.cs @@ -6,115 +6,120 @@ namespace Microsoft.Qwiq.Tests.Common { /// - /// Serves as a base class for implementing Behavior-Driven Development + /// Serves as a base class for implementing Behavior-Driven Development /// /// - /// Behavior-driven development (BDD) is based on test-driven development (TDD). TDD is a software development methodology which essentially states that for each unit of software, a software developer must: - ///
    - ///
  • define a test set for the unit first;
  • - ///
  • then implement the unit;
  • - ///
  • >finally verify that the implementation of the unit makes the tests succeed.
  • - ///
- /// TDD tends to get far into the detail, which doesn't resonate with non-technical individuals. BDD specifies that tests of any unit of software should be specified in terms of the desired behavior of the unit. - /// BDD takes advantage of user stories to represent the intended behavior of components in a vertical slice, experienced as a user or client would experience the behavior. This perspective keeps focus on and allows - /// the engineer to prioritize what is and isn't imporant to test. - /// - /// User Stories represent needed conversations: + /// Behavior-driven development (BDD) is based on test-driven development (TDD). TDD is a software development + /// methodology which essentially states that for each unit of software, a software developer must: + ///
    + ///
  • define a test set for the unit first;
  • + ///
  • then implement the unit;
  • + ///
  • >finally verify that the implementation of the unit makes the tests succeed.
  • + ///
+ /// TDD tends to get far into the detail, which doesn't resonate with non-technical individuals. BDD specifies that + /// tests of any unit of software should be specified in terms of the desired behavior of the unit. + /// BDD takes advantage of user stories to represent the intended behavior of components in a vertical slice, + /// experienced as a user or client would experience the behavior. This perspective keeps focus on and allows + /// the engineer to prioritize what is and isn't imporant to test. + /// User Stories represent needed conversations: /// As a [role] /// I want [some feature / behavior] /// So that [some benefit / value] - /// - /// Scenarios document the conversation took place, and what should be tested in BDD + /// Scenarios document the conversation took place, and what should be tested in BDD /// Given [some initial state / context] /// When [action occurs] /// Then [this should be the result] - /// - /// Example using an e-Commerce site concept: + /// Example using an e-Commerce site concept: /// As a Customer with Products in my Cart /// I want to remove an Item from my Cart /// so that I can change my mind about buying it - /// - /// The scenario of the story breaks down into several elements: - ///
    - ///
  • A case where there are multiple items in the cart and the user removes one, leaving items in the cart
  • - ///
  • A case where there is a single item in the cart and the user removes one, leaving no items in the cart
  • - ///
  • Permutations of the above
  • - ///
- /// - /// BDD helps the clarification in the following way: + /// The scenario of the story breaks down into several elements: + ///
    + ///
  • A case where there are multiple items in the cart and the user removes one, leaving items in the cart
  • + ///
  • A case where there is a single item in the cart and the user removes one, leaving no items in the cart
  • + ///
  • Permutations of the above
  • + ///
+ /// BDD helps the clarification in the following way: /// Given a Cart with multiple Items in it /// When a single Item is removed /// Then the other items are still in the Cart - /// /// Given a Cart with a single Item in it /// When a single Item is removed /// Then the Cart is empty - /// - /// The _Then_ element can be compounded with multiple assertions. For example: - /// + /// The _Then_ element can be compounded with multiple assertions. For example: /// Given a Cart with a single Item in it /// When a single Item is removed /// Then the Cart is empty - /// And the Cart Subtotal is 0 - /// - /// Notice that the language contains nouns with capital letters (Cart, Item, etc.) to drive out a native domain language - /// (see Evans & Fowler (2003). Domain-Driven Design: Tackling complexity in the Heart of Software. Prentice Hall.). The - /// class uses a variation of the Arrange, Act, Assert behavior to allow BDD to work without extra tools within MSTest. - /// - /// Example with code - /// - /// - /// [TestClass] - /// public class When_adding_a_single_item_to_an_empty_cart : ContextSpecification - /// { - /// ICart _cart; - /// - /// protected void override Given() - /// { - /// _cart = CartFactory.Create("TEST"); - /// _cart.AddItem(ProductFactory.Create("SKU")); - /// } - /// - /// protected void override When() - /// { - /// _cart.RemoveItem("SKU"); - /// } - /// - /// [TestMethod] - /// public void then_the_cart_is_empty() - /// { - /// Assert.AreEqual(1, _cart.TotalItems); - /// } - /// - /// [TestMethod] - /// public void then_the_cart_subtotal_is_zero() - /// { - /// Assert.AreEqual(0d, _cart.Subtotal); - /// } - /// } - /// - /// - /// In the generated TRX file from MSTest, the test class and name are specified with the pass result: - /// - /// ------------------------------------------------------------------------------------------------------ - /// Result | Class name | Test Name - /// ------------------------------------------------------------------------------------------------------ - /// Passed When_adding_a_single_item_to_an_empty_cart then_the_cart_is_empty - /// Passed When_adding_a_single_item_to_an_empty_cart then_the_cart_subtotal_is_zero - /// - /// - /// The TRX file is XML, and can be transformed to HTML to generate a report of capabilities of the system automatically - /// during a build activity, yielding a "living spec". - /// - /// More info on BDD: - /// - /// http://en.wikipedia.org/wiki/Behavior-driven_development - /// http://channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DPR302 - /// + /// And the Cart Subtotal is 0 + /// Notice that the language contains nouns with capital letters (Cart, Item, etc.) to drive out a native domain + /// language + /// (see Evans & Fowler (2003). Domain-Driven Design: Tackling complexity in the Heart of Software. Prentice Hall.). + /// The + /// class uses a variation of the Arrange, Act, Assert behavior to allow BDD to work without extra tools within MSTest. + /// Example with code + /// + /// + /// [TestClass] + /// public class When_adding_a_single_item_to_an_empty_cart : ContextSpecification + /// { + /// ICart _cart; + /// + /// protected void override Given() + /// { + /// _cart = CartFactory.Create("TEST"); + /// _cart.AddItem(ProductFactory.Create("SKU")); + /// } + /// + /// protected void override When() + /// { + /// _cart.RemoveItem("SKU"); + /// } + /// + /// [TestMethod] + /// public void then_the_cart_is_empty() + /// { + /// Assert.AreEqual(1, _cart.TotalItems); + /// } + /// + /// [TestMethod] + /// public void then_the_cart_subtotal_is_zero() + /// { + /// Assert.AreEqual(0d, _cart.Subtotal); + /// } + /// } + /// + /// In the generated TRX file from MSTest, the test class and name are specified with the pass result: + /// + /// ------------------------------------------------------------------------------------------------------ + /// Result | Class name | Test Name + /// ------------------------------------------------------------------------------------------------------ + /// Passed When_adding_a_single_item_to_an_empty_cart then_the_cart_is_empty + /// Passed When_adding_a_single_item_to_an_empty_cart then_the_cart_subtotal_is_zero + /// + /// The TRX file is XML, and can be transformed to HTML to generate a report of capabilities of the system + /// automatically + /// during a build activity, yielding a "living spec". + /// More info on BDD: + /// http://en.wikipedia.org/wiki/Behavior-driven_development + /// http://channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DPR302 ///
[DebuggerStepThrough] public abstract class ContextSpecification : IContextSpecification { + public virtual void Cleanup() + { + } + + public virtual void Given() + { + } + + [TestCleanup] + public void TestCleanup() + { + Cleanup(); + } + [TestInitialize] public void TestInitialize() { @@ -126,7 +131,7 @@ public void TestInitialize() catch (Exception e) { // This is very, very bad. - Debug.Print($"{this.GetType().FullName} encountered an exception during Given/When: {e.Message}\r\n{e}"); + Debug.Print($"{GetType().FullName} encountered an exception during Given/When: {e.Message}\r\n{e}"); try { @@ -137,34 +142,12 @@ public void TestInitialize() Debug.Print($"{GetType().FullName} encountered a problem during cleanup: {ex.Message}\r\n{ex}"); } - if (!Debugger.IsAttached) - { - Assert.Fail($"{this.GetType().FullName} encountered an exception: {e.Message}"); - } - else - { - Debugger.Break(); - Assert.Fail($"{this.GetType().FullName} encountered an exception: {e.Message}"); - } + Assert.Fail($"{GetType().FullName} encountered an exception: {e.Message}"); } } - [TestCleanup] - public void TestCleanup() - { - Cleanup(); - } - - public virtual void Given() - { - } - public virtual void When() { } - - public virtual void Cleanup() - { - } } } \ No newline at end of file From d482b57e982a605595b8720801d835ecd686aa19 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 19 Apr 2017 18:30:22 -0700 Subject: [PATCH 111/251] Add test to exercise `Links` property of `IWorkItem` --- src/Qwiq.Core.Rest/LinkCollection.cs | 4 ++ src/Qwiq.Core.Rest/Query.cs | 20 +++--- src/Qwiq.Core.Rest/WorkItem.cs | 67 ++++++++++++++++- src/Qwiq.Core.Rest/WorkItemStore.cs | 6 +- src/Qwiq.Core.Soap/Query.cs | 18 +++-- src/Qwiq.Core.Soap/WorkItem.cs | 8 +-- src/Qwiq.Core.Soap/WorkItemStore.cs | 6 +- src/Qwiq.Core/Comparer.cs | 20 ++++-- src/Qwiq.Core/ExternalLink.cs | 22 +++++- src/Qwiq.Core/IQuery.cs | 4 +- src/Qwiq.Core/IWorkItem.Extensions.cs | 10 ++- src/Qwiq.Core/IWorkItemCollection.cs | 9 +++ src/Qwiq.Core/IWorkItemStore.cs | 4 +- src/Qwiq.Core/Qwiq.Core.csproj | 3 + src/Qwiq.Core/ReadOnlyCollection.cs | 9 ++- src/Qwiq.Core/ReadOnlyCollectionWithId.cs | 7 ++ src/Qwiq.Core/WorkItemCollection.cs | 32 +++++++++ src/Qwiq.Core/WorkItemCollectionComparer.cs | 72 +++++++++++++++++++ .../LargeWiqlHierarchyQueryTests.cs | 4 +- test/Qwiq.Integration.Tests/LinkTests.cs | 51 ++++++++++++- test/Qwiq.Integration.Tests/Result.cs | 14 +++- ...ItemStoreComparisonContextSpecification.cs | 3 + .../Mocks/InstrumentedMockWorkItemStore.cs | 4 +- test/Qwiq.Mocks/MockQueryByWiql.cs | 11 ++- test/Qwiq.Mocks/MockWorkItemStore.cs | 10 +-- 25 files changed, 359 insertions(+), 59 deletions(-) create mode 100644 src/Qwiq.Core/IWorkItemCollection.cs create mode 100644 src/Qwiq.Core/WorkItemCollection.cs create mode 100644 src/Qwiq.Core/WorkItemCollectionComparer.cs diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index adf8635b..657ff1f1 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -35,6 +35,10 @@ public LinkCollection(IEnumerable relations, Func GetLinkTypes() + public IWorkItemLinkTypeEndCollection GetLinkTypes() { //TODO: Verify this is a links query // if (!IsLinkQuery) return null; @@ -79,10 +79,15 @@ public IEnumerable RunLinkQuery() } } - public IEnumerable RunQuery() + public IWorkItemCollection RunQuery() { if (_ids == null && _query == null) throw new InvalidOperationException(); + return new WorkItemCollection(RunQueryImpl().ToList()); + } + + private IEnumerable RunQueryImpl() + { if (_ids == null && _query != null) { var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); @@ -94,13 +99,7 @@ public IEnumerable RunQuery() var expand = WorkItemExpand.All; var qry = _ids.Partition(_workItemStore.PageSize); - var ts = qry.Select( - s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync( - s, - null, - _asOf, - expand, - WorkItemErrorPolicy.Omit)); + var ts = qry.Select(s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, null, _asOf, expand, WorkItemErrorPolicy.Omit)); // This is done in parallel so keep performance similar to the SOAP client foreach (var workItem in Task.WhenAll(ts).GetAwaiter().GetResult().SelectMany(s => s.Select(f => f))) @@ -117,8 +116,7 @@ IWorkItemType WorkItemTypeFactory() yield return ExceptionHandlingDynamicProxyFactory.Create( new WorkItem( workItem, - new Lazy( - WorkItemTypeFactory), + new Lazy(WorkItemTypeFactory), s => _workItemStore.WorkItemLinkTypes[s])); } } diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index 4a87985b..0c55e235 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Qwiq.Rest { @@ -13,7 +14,10 @@ internal class WorkItem : Qwiq.WorkItem private readonly Lazy _wit; - internal WorkItem(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, Lazy wit, Func linkFunc) + internal WorkItem( + TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, + Lazy wit, + Func linkFunc) : base(item.Fields) { _item = item; @@ -36,6 +40,67 @@ public override string Keywords public override ICollection Links => _links.Value; + /// + public override int RelatedLinkCount + { + get + { + var fv = GetValue(CoreFieldRefNames.ExternalLinkCount); + if (!fv.HasValue) + { + var cnt = Links.Count(p => p.BaseType == BaseLinkType.RelatedLink); + SetValue(CoreFieldRefNames.ExternalLinkCount, cnt); + fv = cnt; + } + return fv.GetValueOrDefault(); + } + } + + public override int HyperlinkCount + { + get + { + var fv = GetValue(CoreFieldRefNames.HyperlinkCount); + if (!fv.HasValue) + { + var cnt = Links.Count(p => p.BaseType == BaseLinkType.Hyperlink); + SetValue(CoreFieldRefNames.HyperlinkCount, cnt); + fv = cnt; + } + return fv.GetValueOrDefault(); + } + } + + public override int ExternalLinkCount + { + get + { + var fv = GetValue(CoreFieldRefNames.ExternalLinkCount); + if (!fv.HasValue) + { + var cnt = Links.Count(p => p.BaseType == BaseLinkType.ExternalLink); + SetValue(CoreFieldRefNames.ExternalLinkCount, cnt); + fv = cnt; + } + return fv.GetValueOrDefault(); + } + } + + public override int AttachedFileCount + { + get + { + var fv = GetValue(CoreFieldRefNames.AttachedFileCount); + if (!fv.HasValue) + { + var cnt = Attachments.Count(); + SetValue(CoreFieldRefNames.AttachedFileCount, cnt); + fv = cnt; + } + return fv.GetValueOrDefault(); + } + } + public override int Rev => _item.Rev.GetValueOrDefault(0); public override IWorkItemType Type => _wit.Value; diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 6ea899d8..b6eb083b 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -104,19 +104,19 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable Query(string wiql, bool dayPrecision = false) + public IWorkItemCollection Query(string wiql, bool dayPrecision = false) { // REVIEW: SOAP client catches a ValidationException here var query = _queryFactory.Value.Create(wiql, dayPrecision); return query.RunQuery(); } - public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + public IWorkItemCollection Query(IEnumerable ids, DateTime? asOf = null) { // Same behavior as SOAP version if (ids == null) throw new ArgumentNullException(nameof(ids)); var ids2 = (int[])ids.ToArray().Clone(); - if (!ids2.Any()) return Enumerable.Empty(); + if (!ids2.Any()) return Enumerable.Empty().ToWorkItemCollection(); var query = _queryFactory.Value.Create(ids2, asOf); return query.RunQuery(); diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index fdf7110a..a65dddd0 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -21,9 +21,13 @@ internal Query(TeamFoundation.WorkItemTracking.Client.Query query, int pageSize if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) throw new PageSizeRangeException(); } - public IEnumerable GetLinkTypes() + public IWorkItemLinkTypeEndCollection GetLinkTypes() { - return _query.GetLinkTypes().Select(item => new WorkItemLinkTypeEnd(item)); + return new WorkItemLinkTypeEndCollection( + _query + .GetLinkTypes() + .Select(item => new WorkItemLinkTypeEnd(item)) + .ToList()); } public IEnumerable RunLinkQuery() @@ -40,13 +44,17 @@ public IEnumerable RunLinkQuery() }); } - public IEnumerable RunQuery() + public IWorkItemCollection RunQuery() { var wic = _query.RunQuery(); wic.PageSize = _pageSize; - return wic.Cast() - .Select(item => ExceptionHandlingDynamicProxyFactory.Create((WorkItem)item)); + return + wic + .Cast() + .Select(item => ExceptionHandlingDynamicProxyFactory.Create((WorkItem)item)) + .ToList() + .ToWorkItemCollection(); } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/WorkItem.cs b/src/Qwiq.Core.Soap/WorkItem.cs index 69ba1bfc..28fe9520 100644 --- a/src/Qwiq.Core.Soap/WorkItem.cs +++ b/src/Qwiq.Core.Soap/WorkItem.cs @@ -253,10 +253,10 @@ public override void Open() /// /// Opens this work item for modification when transmitting minimal amounts of data over the network. /// - /// + /// /// This WorkItem instance does not belong to a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemCollection. /// - /// + /// /// This WorkItem instance could not be opened for edit correctly. /// public override void PartialOpen() @@ -331,7 +331,7 @@ public static implicit operator WorkItem(Tfs.WorkItem workItem) /// A new WorkItem instance of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType /// that is a copy of this WorkItem instance. /// - /// + /// /// Thrown when targetType is null. /// public IWorkItem Copy(IWorkItemType targetType) @@ -350,7 +350,7 @@ public IWorkItem Copy(IWorkItemType targetType) /// A new WorkItem instance of the specified Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType /// that is a copy of this WorkItem instance. /// - /// + /// /// Thrown when targetType is null. /// public IWorkItem Copy(IWorkItemType targetType, WorkItemCopyFlags flags) diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index f2738bb8..4c9cc7ad 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -100,7 +100,7 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable Query(string wiql, bool dayPrecision = true) + public IWorkItemCollection Query(string wiql, bool dayPrecision = true) { try { @@ -113,11 +113,11 @@ public IEnumerable Query(string wiql, bool dayPrecision = true) } } - public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + public IWorkItemCollection Query(IEnumerable ids, DateTime? asOf = null) { if (ids == null) throw new ArgumentNullException(nameof(ids)); var ids2 = (int[])ids.ToArray().Clone(); - if (!ids2.Any()) return Enumerable.Empty(); + if (!ids2.Any()) return Enumerable.Empty().ToWorkItemCollection(); try { diff --git a/src/Qwiq.Core/Comparer.cs b/src/Qwiq.Core/Comparer.cs index cae1b11f..76c36ad8 100644 --- a/src/Qwiq.Core/Comparer.cs +++ b/src/Qwiq.Core/Comparer.cs @@ -1,22 +1,28 @@ using System; +using System.Collections.Generic; namespace Microsoft.Qwiq { public static class Comparer { - public static readonly ReadOnlyCollectionWithIdComparer FieldCollection = + public static IEqualityComparer> FieldCollection = ReadOnlyCollectionWithIdComparer.Default; - public static readonly FieldDefinitionCollectionComparer FieldDefinitionCollection = FieldDefinitionCollectionComparer.Default; + public static IEqualityComparer FieldDefinitionCollection = FieldDefinitionCollectionComparer.Default; - public static readonly IdentifiableComparer Identifiable = IdentifiableComparer.Default; + public static IEqualityComparer> Identifiable = IdentifiableComparer.Default; - public static readonly IdentityDescriptorComparer IdentityDescriptor = IdentityDescriptorComparer.Default; + public static IEqualityComparer IdentityDescriptor = IdentityDescriptorComparer.Default; - public static readonly ReadOnlyCollectionWithIdComparer NodeCollection = + public static IEqualityComparer> NodeCollection = ReadOnlyCollectionWithIdComparer.Default; - public static readonly WorkItemLinkTypeEndComparer WorkItemLinkTypeEnd = WorkItemLinkTypeEndComparer.Default; - public static readonly StringComparer OrdinalIgnoreCase = StringComparer.OrdinalIgnoreCase; + public static IEqualityComparer OrdinalIgnoreCase = StringComparer.OrdinalIgnoreCase; + + public static IEqualityComparer WorkItem = WorkItemComparer.Default; + + public static IEqualityComparer WorkItemLinkTypeEnd = WorkItemLinkTypeEndComparer.Default; + + public static IEqualityComparer WorkItemCollection = WorkItemCollectionComparer.Default; } } \ No newline at end of file diff --git a/src/Qwiq.Core/ExternalLink.cs b/src/Qwiq.Core/ExternalLink.cs index 223a5d00..c46d1a96 100644 --- a/src/Qwiq.Core/ExternalLink.cs +++ b/src/Qwiq.Core/ExternalLink.cs @@ -7,8 +7,26 @@ internal class ExternalLink : Link, IExternalLink public ExternalLink(string uri, string name, string comment = null) : base(comment, BaseLinkType.ExternalLink) { - if (string.IsNullOrEmpty(uri)) throw new ArgumentException("Value cannot be null or empty.", nameof(uri)); - if (string.IsNullOrEmpty(name)) throw new ArgumentException("Value cannot be null or empty.", nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (name.Trim().Length < 1) throw new ArgumentNullException(nameof(name)); + + if (uri == null) throw new ArgumentNullException(nameof(uri)); + if (uri.Trim().Length < 1) throw new ArgumentNullException(nameof(uri)); + + if (uri.Length > 2083) throw new ArgumentException("Uri too long."); + + if (Comparer.OrdinalIgnoreCase.Equals("Related Workitem", name) + || Comparer.OrdinalIgnoreCase.Equals("Workitem Hyperlink", name)) + { + throw new ArgumentException(nameof(name)); + } + + if (Comparer.OrdinalIgnoreCase.Equals("Fixed in Changeset", name) + || Comparer.OrdinalIgnoreCase.Equals("Source Code File", name) + || Comparer.OrdinalIgnoreCase.Equals("Test Result", name)) + { + throw new ArgumentException(nameof(uri)); + } LinkedArtifactUri = uri; ArtifactLinkTypeName = name; diff --git a/src/Qwiq.Core/IQuery.cs b/src/Qwiq.Core/IQuery.cs index 630f2ec9..df33da8c 100644 --- a/src/Qwiq.Core/IQuery.cs +++ b/src/Qwiq.Core/IQuery.cs @@ -11,7 +11,7 @@ public interface IQuery /// Gets a collection of objects associated with this query. /// /// IEnumerable<IWorkItemLinkTypeEnd>. - IEnumerable GetLinkTypes(); + IWorkItemLinkTypeEndCollection GetLinkTypes(); /// /// Executes a query that gets a collection of objects that satisfy the WIQL. @@ -23,6 +23,6 @@ public interface IQuery /// Executes a query that gets a collection of objects that satisfy the WIQL. /// /// IEnumerable<IWorkItem>. - IEnumerable RunQuery(); + IWorkItemCollection RunQuery(); } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItem.Extensions.cs b/src/Qwiq.Core/IWorkItem.Extensions.cs index 1ffbcbb5..6c48aef7 100644 --- a/src/Qwiq.Core/IWorkItem.Extensions.cs +++ b/src/Qwiq.Core/IWorkItem.Extensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace Microsoft.Qwiq { @@ -15,8 +17,12 @@ public static void AddRelatedLink(this IWorkItem workItem, IWorkItemStore store, foreach (var id in targets) workItem.Links.Add(workItem.CreateRelatedLink(id, end)); } - + public static IWorkItemCollection ToWorkItemCollection(this IEnumerable items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (items is IWorkItemCollection items2) return items2; - + return new WorkItemCollection(items.Distinct(Comparer.WorkItem)); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemCollection.cs b/src/Qwiq.Core/IWorkItemCollection.cs new file mode 100644 index 00000000..2c5f0d6f --- /dev/null +++ b/src/Qwiq.Core/IWorkItemCollection.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.Qwiq +{ + public interface IWorkItemCollection : IReadOnlyCollectionWithId, IEquatable + { + + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 9ca4a9b3..dde3ef0c 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -79,7 +79,7 @@ public interface IWorkItemStore : IDisposable /// false. /// /// IEnumerable<IWorkItem>. - IEnumerable Query(string wiql, bool dayPrecision = false); + IWorkItemCollection Query(string wiql, bool dayPrecision = false); ///
/// Queries the specified work item IDs. @@ -87,7 +87,7 @@ public interface IWorkItemStore : IDisposable /// A collection of work item IDs. /// Optional: The date of the desired work item state. /// IEnumerable<IWorkItem>. - IEnumerable Query(IEnumerable ids, DateTime? asOf = null); + IWorkItemCollection Query(IEnumerable ids, DateTime? asOf = null); /// /// Queries the specified work item ID. diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index cd46cbb1..04c5e6b3 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -169,6 +169,7 @@ + @@ -206,6 +207,8 @@ + + diff --git a/src/Qwiq.Core/ReadOnlyCollection.cs b/src/Qwiq.Core/ReadOnlyCollection.cs index c98c6bec..d2176a5e 100644 --- a/src/Qwiq.Core/ReadOnlyCollection.cs +++ b/src/Qwiq.Core/ReadOnlyCollection.cs @@ -33,8 +33,15 @@ protected ReadOnlyCollection(Func> itemFactory, Func n } protected ReadOnlyCollection(IEnumerable items, Func nameFunc) - : this(() => items ?? Enumerable.Empty(), nameFunc) { + ItemFactory = () => items ?? Enumerable.Empty(); + _nameFunc = nameFunc; + } + + protected ReadOnlyCollection(IEnumerable items) + :this(items, null) + { + } protected ReadOnlyCollection() diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs index caedf7a9..af7d33c3 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs @@ -21,6 +21,13 @@ protected ReadOnlyCollectionWithId(IEnumerable items, Func nameFun _mapById = new Dictionary(); } + protected ReadOnlyCollectionWithId(IEnumerable items) + :base(items) + { + _idFunc = a => a.Id; + _mapById = new Dictionary(); + } + public virtual bool Contains(TId id) { Ensure(); diff --git a/src/Qwiq.Core/WorkItemCollection.cs b/src/Qwiq.Core/WorkItemCollection.cs new file mode 100644 index 00000000..301094d1 --- /dev/null +++ b/src/Qwiq.Core/WorkItemCollection.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Microsoft.Qwiq +{ + public class WorkItemCollection : ReadOnlyCollectionWithId, IWorkItemCollection + { + public WorkItemCollection(IEnumerable workItems) + :base(workItems) + { + } + + /// + public bool Equals(IWorkItemCollection other) + { + return Comparer.WorkItemCollection.Equals(this, other); + } + + /// + public override bool Equals(object obj) + { + return Comparer.WorkItemCollection.Equals(this, obj as IWorkItemCollection); + } + + /// + public override int GetHashCode() + { + return Comparer.WorkItemCollection.GetHashCode(this); + } + + + } +} diff --git a/src/Qwiq.Core/WorkItemCollectionComparer.cs b/src/Qwiq.Core/WorkItemCollectionComparer.cs new file mode 100644 index 00000000..5673fbe4 --- /dev/null +++ b/src/Qwiq.Core/WorkItemCollectionComparer.cs @@ -0,0 +1,72 @@ +using System.Linq; + +namespace Microsoft.Qwiq +{ + public class WorkItemCollectionComparer : GenericComparer + { + internal new static WorkItemCollectionComparer Default => Nested.Instance; + + private WorkItemCollectionComparer() + { + } + + /// + public override bool Equals(IWorkItemCollection x, IWorkItemCollection y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + var source = y.ToList(); + var expected = x.ToList(); + + if (source.Count != expected.Count) return false; + + foreach (var item in expected) + { + if (!y.Contains(item.Id)) return false; + + var tf = y.GetById(item.Id); + if (!Comparer.WorkItem.Equals(item, tf)) return false; + + // Removes the first occurrence, so if there are duplicates we'll still get a valid mismatch + source.Remove(item); + } + + // If there are any items left then fail + if (source.Any()) return false; + + return true; + } + + /// + public override int GetHashCode(IWorkItemCollection obj) + { + if (ReferenceEquals(obj, null)) return 0; + var hash = 27; + foreach (var item in obj.OrderBy(p => p.Id)) + { + var itemHash = item.GetHashCode(); + hash = (13 * hash) ^ itemHash; + } + return hash; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + private class Nested + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly WorkItemCollectionComparer Instance = + new WorkItemCollectionComparer(); + + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs index 5ff21b19..35fe1e1f 100644 --- a/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs @@ -32,7 +32,7 @@ AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', "REST", "QueryLinks - WIQL"); RestResult.WorkItems = TimedAction( - () => RestResult.WorkItemStore.Query(new HashSet(RestResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId }).Where(i => i != 0))).ToList(), + () => RestResult.WorkItemStore.Query(new HashSet(RestResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId }).Where(i => i != 0))), "REST", "Query - IDs"); @@ -43,7 +43,7 @@ AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', "SOAP", "QueryLinks - WIQL"); SoapResult.WorkItems = TimedAction( - () => SoapResult.WorkItemStore.Query(new HashSet(SoapResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId }).Where(i => i != 0))).ToList(), + () => SoapResult.WorkItemStore.Query(new HashSet(SoapResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId }).Where(i => i != 0))), "SOAP", "Query - IDs"); } diff --git a/test/Qwiq.Integration.Tests/LinkTests.cs b/test/Qwiq.Integration.Tests/LinkTests.cs index 525bd904..60d4da78 100644 --- a/test/Qwiq.Integration.Tests/LinkTests.cs +++ b/test/Qwiq.Integration.Tests/LinkTests.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -8,20 +10,65 @@ namespace Microsoft.Qwiq.Integration.Tests public class Given_a_WorkItem_with_Links : WorkItemWithLinksContextSpecification { [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] public void links_from_both_implementations_are_equal() { RestResult.WorkItem.Links.ShouldContainOnly(SoapResult.WorkItem.Links); } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + public void ExternalLinkCount_is_equal() + { + RestResult.WorkItem.ExternalLinkCount.ShouldEqual(SoapResult.WorkItem.ExternalLinkCount); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + public void HyperlinkCount_is_equal() + { + RestResult.WorkItem.HyperlinkCount.ShouldEqual(SoapResult.WorkItem.HyperlinkCount); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + [ExpectedException(typeof(NotSupportedException))] + public void AttachedFileCount_is_equal() + { + RestResult.WorkItem.AttachedFileCount.ShouldEqual(SoapResult.WorkItem.AttachedFileCount); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("REST")] + [TestCategory("SOAP")] + public void RelatedLinkCount_is_equal() + { + RestResult.WorkItem.RelatedLinkCount.ShouldEqual(SoapResult.WorkItem.RelatedLinkCount); + } } public abstract class WorkItemWithLinksContextSpecification : WorkItemStoreComparisonContextSpecification { - private const int Id = 123456; + private const int Id = 104268; public override void When() { SoapResult.WorkItem = TimedAction(() => SoapResult.WorkItemStore.Query(Id), "SOAP", "Query By Id"); RestResult.WorkItem = TimedAction(() => RestResult.WorkItemStore.Query(Id), "REST", "Query By Id"); + + SoapResult.WorkItem.ExternalLinkCount.ShouldBeGreaterThan(0); + SoapResult.WorkItem.HyperlinkCount.ShouldBeGreaterThan(0); + SoapResult.WorkItem.AttachedFileCount.ShouldBeGreaterThan(0); + SoapResult.WorkItem.RelatedLinkCount.ShouldBeGreaterThan(0); } } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/Result.cs b/test/Qwiq.Integration.Tests/Result.cs index 69c08f9f..99a346bc 100644 --- a/test/Qwiq.Integration.Tests/Result.cs +++ b/test/Qwiq.Integration.Tests/Result.cs @@ -5,9 +5,19 @@ namespace Microsoft.Qwiq.Integration.Tests { public class Result : IDisposable { - public IWorkItem WorkItem { get; set; } + private IWorkItem _workItem; - public IEnumerable WorkItems { get; set; } + public IWorkItem WorkItem + { + get => _workItem; + set + { + _workItem = value; + WorkItems = new WorkItemCollection(new[]{value}); + } + } + + public IWorkItemCollection WorkItems { get; set; } public IEnumerable Links { get; set; } diff --git a/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs index 22461d8c..81822d58 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs @@ -18,6 +18,9 @@ public void WorkItem_is_equal() } } + [DeploymentItem("Microsoft.WITDataStore32.dll")] + [DeploymentItem("Microsoft.WITDataStore64.dll")] + [DeploymentItem("Microsoft.TeamFoundation.WorkItemTracking.Client.dll")] public abstract class WorkItemStoreComparisonContextSpecification : TimedContextSpecification { protected IWorkItemStore Rest => RestResult.WorkItemStore; diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index cb368a60..99aec18d 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -78,13 +78,13 @@ public void Dispose() _innerWorkItemStore.Dispose(); } - public IEnumerable Query(string wiql, bool dayPrecision = false) + public IWorkItemCollection Query(string wiql, bool dayPrecision = false) { QueryStringCallCount += 1; return _innerWorkItemStore.Query(wiql, dayPrecision); } - public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + public IWorkItemCollection Query(IEnumerable ids, DateTime? asOf = null) { QueryIdsCallCount += 1; return _innerWorkItemStore.Query(ids, asOf); diff --git a/test/Qwiq.Mocks/MockQueryByWiql.cs b/test/Qwiq.Mocks/MockQueryByWiql.cs index 86ae9211..ab6572b3 100644 --- a/test/Qwiq.Mocks/MockQueryByWiql.cs +++ b/test/Qwiq.Mocks/MockQueryByWiql.cs @@ -69,7 +69,7 @@ public MockQueryByWiql(string query, MockWorkItemStore store) } } - public IEnumerable GetLinkTypes() + public IWorkItemLinkTypeEndCollection GetLinkTypes() { // TODO: Limit IWorkItemLinkTypeEnds to the links contained in WIQL return _store.WorkItemLinkTypes.LinkTypeEnds; @@ -130,7 +130,7 @@ public IEnumerable RunLinkQuery() // var workItem = "Source".Equals(fieldPredicate.Item1, StringComparison.OrdinalIgnoreCase) ? source : target; // match = MatchAggregate(match, fieldPredicate, workItem); - + //} } else @@ -164,7 +164,12 @@ private static bool MatchAggregate(bool m, Tuple e, IWor return m; } - public IEnumerable RunQuery() + public IWorkItemCollection RunQuery() + { + return new WorkItemCollection(RunQueryImpl()); + } + + private IEnumerable RunQueryImpl() { if (_ids != null) { diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index d5913198..3205d2b8 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -84,26 +84,26 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable Query(string wiql, bool dayPrecision = false) + public IWorkItemCollection Query(string wiql, bool dayPrecision = false) { Trace.TraceInformation("Querying for work items " + wiql); var query = _queryFactory.Value.Create(wiql, dayPrecision); - return query.RunQuery().ToList().AsReadOnly(); + return query.RunQuery(); } - public IEnumerable Query(IEnumerable ids, DateTime? asOf = null) + public IWorkItemCollection Query(IEnumerable ids, DateTime? asOf = null) { if (ids == null) throw new ArgumentNullException(nameof(ids)); var ids2 = (int[])ids.ToArray().Clone(); - if (!ids2.Any()) return Enumerable.Empty(); + if (!ids2.Any()) return Enumerable.Empty().ToWorkItemCollection(); Trace.TraceInformation("Querying for IDs " + string.Join(", ", ids2)); var query = _queryFactory.Value.Create(ids2, asOf); - return query.RunQuery().ToList().AsReadOnly(); + return query.RunQuery(); } public IWorkItem Query(int id, DateTime? asOf = null) From 7424b16403998d461145d27b3006a297349a7333 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:07:01 -0700 Subject: [PATCH 112/251] Code clean up Integration tests and test common --- .../Linq/Visitors/IdentityMapper.cs | 6 +- .../GlobalSuppressions.cs | Bin 0 -> 5696 bytes .../IntegrationSettings.cs | 38 +-- .../LargeWiqlHierarchyQueryTests.cs | 7 +- .../Qwiq.Integration.Tests.csproj | 1 + test/Qwiq.Integration.Tests/Result.cs | 21 +- .../WiqlFlatQueryTests.cs | 4 +- .../WiqlHierarchyQueryTests.cs | 2 - ...ItemStoreComparisonContextSpecification.cs | 3 +- .../WorkItemStoreFactoryTests.cs | 38 ++- .../WorkItemStoreTests.cs | 261 ++---------------- test/Qwiq.Integration.Tests/WorkItemTests.cs | 8 +- test/Qwiq.Tests.Common/Clock.cs | 2 +- .../TimedContextSpecification.cs | 27 +- 14 files changed, 118 insertions(+), 300 deletions(-) create mode 100644 test/Qwiq.Integration.Tests/GlobalSuppressions.cs diff --git a/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs b/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs index ad37ecb2..072d4bb3 100644 --- a/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs +++ b/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs @@ -24,14 +24,12 @@ private string[] GetDisplayNames(params string[] aliases) public object Map(object value) { - var stringValue = value as string; - if (stringValue != null) + if (value is string stringValue) { return GetDisplayNames(stringValue).Single(); } - var stringArray = value as IEnumerable; - if (stringArray != null) + if (value is IEnumerable stringArray) { return GetDisplayNames(stringArray.ToArray()); } diff --git a/test/Qwiq.Integration.Tests/GlobalSuppressions.cs b/test/Qwiq.Integration.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..f68dc3f5dde6b32cce4833031a4cfa2fc136b0c7 GIT binary patch literal 5696 zcmeHLTWb?R6h6;_|6xd9T4=Y{Rs>O@_71hSBw9oyq{+6fO?Tt&My)?y{l0TD*~v}n zgAz0p-R#Wlxqjz1Kfb?{mK6Altcjkl%Lh>DgMUX3A;)K|zEOD+tkgl7TvAXy!Rn2EQCBARP9UECV{8=>K)TAQSFXlIeIKt-3w@5+2s^#IjG0#vmC!;Sie-C&8IEQ zSEgBLjSYDWBy7PNM~b}^S|q>^u|N$&tzTfR4iv`Fvy1;bQ?)0~M)66?PM#_L&g2Vd ze*zNc5i#>(rE|4W7gnM*>%dl~cK22+kx%Fc+ixfunF-3Fou}mkZ~F+ork`WPFwsqq zGomADV2yc-Ip=st)k||YKn1biMl{^Y8RCXGxedOYPtP6zu^Hl=c{P!5f!3qZu!(4R zglI6Fw{i9o+8xP0zRr>DXsuKH%8(bC52?LTn2q_->>uWsXoT)ExQ2BiJh?iy_X@gv z1b1egm6LRtJ zjbrR3INQ@4Lk)=45T#49H>ARvm<=t_)JU=O%yk85mEcC2tOcYt$+-cmGn#ms*@<6F z`)(h5%zYiLtjv+Ap0?;IP>1P>dCY77GWr*hQRVMNWc+TAyr}q9#sA;xJZFGiR1b$d zo+0ZyQ~{gboOTIe_wRIv>ltc{?;_X(+gI$PSY7O!;toAQHBCekR&zg-<)F1EUzH*& znXfXO2CHvRRC0FaCpxor<>}N}Fzo%tE3dDA&r!h|=s0J0A&X9<>S@@MFFK98-Z}8T zVH39w7IB8!9mGtj-QWT6o8q6%+C+UDZ$0c9>ODezTun|yf2-4-J~{E`#wIGpYdAab zR;KD*XYWL^G?=Di%mlvAWJk(ri(PAsskx80?pU9>F6+oa4devg6*-;VIi0(~dmpQo zr!LUvI_1K-#ZBBZImpgz$SH@tExT?$If4G_{>kahkI6w@c|%m)sZb_%)ZAefr8a!> zK8v%M$!45+&a@cq>!W~!OimdQ#XYp8G< wXVknFSk%c|NYjT_9kr CreateRestStore = () => - { - var options = - new AuthenticationOptions( - Uri, - AuthenticationTypes.Windows, - ClientType.Rest); - return WorkItemStoreFactory.Instance.Create(options); - }; - - public static readonly Func CreateSoapStore = () => - { - var options = - new AuthenticationOptions( - Uri, - AuthenticationTypes.Windows, - ClientType.Soap); - return WorkItemStoreFactory.Instance.Create(options); - }; + public static Func CreateSoapStore { get; } = () => + { + var options = + new AuthenticationOptions( + Uri, + AuthenticationTypes.Windows, + ClientType.Soap); + return WorkItemStoreFactory.Instance.Create(options); + }; private static readonly Uri Uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); + + public static Func CreateRestStore { get; } = () => + { + var options = + new AuthenticationOptions( + Uri, + AuthenticationTypes.Windows, + ClientType.Rest); + return WorkItemStoreFactory.Instance.Create(options); + }; } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs index 35fe1e1f..9e4ed841 100644 --- a/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/LargeWiqlHierarchyQueryTests.cs @@ -1,9 +1,6 @@ -using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using Microsoft.Qwiq.Core.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -26,7 +23,7 @@ AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', mode(Recursive) "; - var start = Clock.GetTimestamp(); + RestResult.Links = TimedAction( () => RestResult.WorkItemStore.QueryLinks(WIQL).ToList(), "REST", @@ -63,7 +60,7 @@ public void Link_Count_Equal() [TestCategory("REST")] public void WorkItem_Count_Equal() { - RestResult.WorkItems.Count().ShouldEqual(SoapResult.WorkItems.Count(), "WorkItems.Count"); + RestResult.WorkItems.Count.ShouldEqual(SoapResult.WorkItems.Count(), "WorkItems.Count"); } [TestMethod] diff --git a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj index 3cbc58d9..502e6b95 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj @@ -203,6 +203,7 @@ + diff --git a/test/Qwiq.Integration.Tests/Result.cs b/test/Qwiq.Integration.Tests/Result.cs index 99a346bc..6ffb7d3f 100644 --- a/test/Qwiq.Integration.Tests/Result.cs +++ b/test/Qwiq.Integration.Tests/Result.cs @@ -7,25 +7,36 @@ public class Result : IDisposable { private IWorkItem _workItem; + public IEnumerable Links { get; set; } + public IWorkItem WorkItem { get => _workItem; set { _workItem = value; - WorkItems = new WorkItemCollection(new[]{value}); + WorkItems = new WorkItemCollection(new[] { value }); } } public IWorkItemCollection WorkItems { get; set; } - public IEnumerable Links { get; set; } - public IWorkItemStore WorkItemStore { get; set; } public void Dispose() { - WorkItemStore?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) WorkItemStore?.Dispose(); + + WorkItemStore = null; + _workItem = null; + WorkItems = null; + Links = null; } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WiqlFlatQueryTests.cs b/test/Qwiq.Integration.Tests/WiqlFlatQueryTests.cs index 95fafb0f..f714416f 100644 --- a/test/Qwiq.Integration.Tests/WiqlFlatQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WiqlFlatQueryTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Globalization; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -9,7 +11,7 @@ public class Given_a_WorkItem_from_each_client_by_WIQL : SingleWorkItemCompariso { private const int Id = 10726528; - private static readonly string Wiql = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems WHERE [System.Id] = {Id}"; + private static readonly string Wiql = ((FormattableString)$"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems WHERE [System.Id] = {Id}").ToString(CultureInfo.InvariantCulture); public override void When() { diff --git a/test/Qwiq.Integration.Tests/WiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/WiqlHierarchyQueryTests.cs index 3e8f330c..82aa6588 100644 --- a/test/Qwiq.Integration.Tests/WiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WiqlHierarchyQueryTests.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs index 81822d58..22957a86 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStoreComparisonContextSpecification.cs @@ -1,4 +1,4 @@ -using Microsoft.Qwiq.Core.Tests; +using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -39,6 +39,7 @@ public override void Cleanup() base.Cleanup(); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public override void Given() { SoapResult = new Result { WorkItemStore = TimedAction(() => IntegrationSettings.CreateSoapStore(), "SOAP", "Create WIS") }; diff --git a/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs index e1e1765f..b687d94d 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs @@ -2,6 +2,7 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Soap; +using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.Services.Client; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -10,45 +11,55 @@ namespace Microsoft.Qwiq.Integration.Tests { - public abstract class WorkItemStoreFactoryContextSpecification : WorkItemStoreTests + public abstract class WorkItemStoreFactoryContextSpecification : TimedContextSpecification { protected IWorkItemStoreFactory Instance { get; private set; } + protected IWorkItemStore WorkItemStore { get; private set; } + public override void Given() { Instance = WorkItemStoreFactory.Instance; + WorkItemStore = TimedAction(Create, "SOAP", "WIS Create"); base.Given(); } + + public override void Cleanup() { + TimedAction(()=> WorkItemStore?.Dispose(), "SOAP", "WIS Dispose"); + base.Cleanup(); - WorkItemStore?.Dispose(); + } - protected override IWorkItemStore Create() + public abstract IWorkItemStore Create(); + } + + [TestClass] + public class Given_a_Uri_and_Credential : WorkItemStoreFactoryContextSpecification + { + public override IWorkItemStore Create() { var uri = new Uri("https://microsoft.visualstudio.com/DefaultCollection"); var cred = new VssClientCredentials( - new WindowsCredential(true), - CredentialPromptType.PromptIfNeeded) + new WindowsCredential(true), + CredentialPromptType.PromptIfNeeded) { Storage = new VssClientCredentialStorage() }; #pragma warning disable CS0618 // Type or member is obsolete return Instance.Create( - uri, - new TfsCredentials(cred)); + uri, + new TfsCredentials(cred)); #pragma warning restore CS0618 // Type or member is obsolete } - } - [TestClass] - public class Given_a_Uri_and_Credential : WorkItemStoreFactoryContextSpecification - { [TestMethod] [TestCategory("localOnly")] + [TestCategory("SOAP")] public void Store_is_Created() { WorkItemStore.ShouldNotBeNull(); @@ -58,7 +69,7 @@ public void Store_is_Created() [TestClass] public class Given_a_Uri_and_Credentials_from_the_CredentialsFactory : WorkItemStoreFactoryContextSpecification { - protected override IWorkItemStore Create() + public override IWorkItemStore Create() { var uri = new Uri("https://microsoft.visualstudio.com/DefaultCollection"); @@ -70,10 +81,11 @@ protected override IWorkItemStore Create() #pragma warning restore CS0618 // Type or member is obsolete } - + [TestMethod] [TestCategory("localOnly")] + [TestCategory("SOAP")] public void Store_is_Created() { WorkItemStore.ShouldNotBeNull(); diff --git a/test/Qwiq.Integration.Tests/WorkItemStoreTests.cs b/test/Qwiq.Integration.Tests/WorkItemStoreTests.cs index e32feb87..7e7315d4 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStoreTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStoreTests.cs @@ -1,248 +1,25 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; +using Microsoft.Qwiq.Tests.Common; -//using Microsoft.Qwiq.Core.Tests; -//using Microsoft.Qwiq.Soap; -//using Microsoft.VisualStudio.TestTools.UnitTesting; - -//using Should; -//using Should.Core.Exceptions; - -//namespace Microsoft.Qwiq.Integration.Tests -//{ -// [TestClass] -// public class Given_a_WorkItemStore_from_each_implementation : WorkItemStoreComparisonContextSpecification -// { -// [TestMethod] -// [TestCategory("localOnly")] -// [TestCategory("REST")] -// [TestCategory("SOAP")] -// [ExpectedException(typeof(EqualException), "No EqualityComparer for TfsCredentials")] -// public void AuthorizedCredentials_are_equal() -// { -// Rest.AuthorizedCredentials.ShouldEqual(Soap.AuthorizedCredentials); -// } - -// [TestMethod] -// [TestCategory("localOnly")] -// [TestCategory("REST")] -// [TestCategory("SOAP")] -// [ExpectedException(typeof(NotImplementedException), "REST does not have an implementation")] -// public void FieldDefinitions_are_equal() -// { -// Rest.FieldDefinitions.ShouldContainOnly(Soap.FieldDefinitions); -// } - -// [TestMethod] -// [TestCategory("localOnly")] -// [TestCategory("REST")] -// [TestCategory("SOAP")] -// public void UserSid_are_equal() -// { -// Rest.UserSid.ShouldEqual(Soap.UserSid); -// } -// } - -// [TestClass] -// public class Given_SOAP_and_REST_workitemstore_implementations : WorkItemStoreComparisonContextSpecification -// { -// public override void When() -// { -// SoapLinkTypes = TimedAction(() => Qwiq.Soap.WorkItemLinkTypes, "SOAP", "Work Item Link Types"); -// RestLinkTypes = TimedAction(() => Qwiq.Rest.WorkItemLinkTypes, "REST", "Work Item Link Types"); -// } - -// [TestMethod] -// [TestCategory("localOnly")] -// [TestCategory("SOAP")] -// [TestCategory("REST")] -// public void WorkItemLinkTypeCollections_are_equal() -// { -// RestLinkTypes.ShouldContainOnly(SoapLinkTypes); -// } - -// public WorkItemLinkTypeCollection RestLinkTypes { get; set; } - -// public WorkItemLinkTypeCollection SoapLinkTypes { get; set; } -// } - -// [TestClass] -// public class given_an_IWorkItemStore_an_a_query_with_two_ids_and_an_asof_date : WorkItemStoreSoapTests -// { -// private IEnumerable _actual; - -// [TestMethod] -// public void a_query_is_created() -// { -// ((MockQueryFactory)QueryFactory).CreateCallCount.ShouldEqual(1); -// } - -// [TestMethod] -// public void a_query_string_with_two_ids_is_generated() -// { -// ((MockQueryFactory)QueryFactory) -// .QueryWiqls.Single() -// .ShouldEqual("SELECT * FROM WorkItems ASOF '2012-01-12 12:23:34Z'"); -// } - -// public override void Given() -// { -// QueryFactory = new MockQueryFactory(); -// base.Given(); -// } - -// public override void When() -// { -// _actual = WorkItemStore.Query(new[] { 1, 2 }, new DateTime(2012, 1, 12, 12, 23, 34, DateTimeKind.Utc)); -// } -// } - -// [TestClass] -// public class given_an_IWorkItemStore_and_a_query_with_no_ids : WorkItemStoreSoapTests -// { -// private IEnumerable _actual; - -// [TestMethod] -// public void a_query_is_never_created() -// { -// ((MockQueryFactory)QueryFactory).CreateCallCount.ShouldEqual(0); -// } - -// [TestMethod] -// public void an_empty_result_set_is_returned() -// { -// _actual.ShouldBeEmpty(); -// } - -// public override void Given() -// { -// QueryFactory = new MockQueryFactory(); -// base.Given(); -// } - -// public override void When() -// { -// _actual = WorkItemStore.Query(new int[] { }); -// } -// } - -// [TestClass] -// public class given_an_IWorkItemStore_and_a_query_with_one_id : WorkItemStoreSoapTests -// { -// private IEnumerable _actual; - -// [TestMethod] -// public void a_query_is_created() -// { -// ((MockQueryFactory)QueryFactory).CreateCallCount.ShouldEqual(1); -// } - -// [TestMethod] -// public void a_query_string_with_one_id_is_generated() -// { -// ((MockQueryFactory)QueryFactory) -// .QueryWiqls.Single() -// .ShouldEqual("SELECT * FROM WorkItems"); -// } - -// public override void Given() -// { -// QueryFactory = new MockQueryFactory(); -// base.Given(); -// } - -// public override void When() -// { -// _actual = WorkItemStore.Query(new[] { 1 }); -// } -// } - -// [TestClass] -// public class given_an_IWorkItemStore_and_a_query_with_two_ids : WorkItemStoreSoapTests -// { -// private IEnumerable _actual; - -// [TestMethod] -// public void a_query_is_created() -// { -// ((MockQueryFactory)QueryFactory).CreateCallCount.ShouldEqual(1); -// } - -// [TestMethod] -// public void a_query_string_with_two_ids_is_generated() -// { -// ((MockQueryFactory)QueryFactory) -// .QueryWiqls.Single() -// .ShouldEqual("SELECT * FROM WorkItems"); -// } - -// public override void Given() -// { -// QueryFactory = new MockQueryFactory(); -// base.Given(); -// } - -// public override void When() -// { -// _actual = WorkItemStore.Query(new[] { 1, 2 }); -// } -// } - -// public abstract class WorkItemStoreSoapTests : WorkItemStoreTests -// { -// protected override IWorkItemStore Create() -// { -// return new WorkItemStore((TfsTeamProjectCollection)null, s => QueryFactory); -// } -// } - -// [TestClass] -// public class LinkTypeTests : WorkItemStoreComparisonContextSpecification -// { -// private IEnumerable _restResult; - -// private IEnumerable _soapResult; - -// [TestMethod] -// [TestCategory("localOnly")] -// [TestCategory("SOAP")] -// [TestCategory("REST")] -// public void Equal() -// { -// _restResult.ShouldContainOnly(_soapResult); -// } - -// public override void When() -// { -// _soapResult = Soap.WorkItemLinkTypes.ToList(); -// _restResult = Rest.WorkItemLinkTypes.ToList(); -// } -// } - -using Microsoft.Qwiq; -using Microsoft.Qwiq.Core.Tests; - -public abstract class WorkItemStoreTests : TimedContextSpecification - where T : IWorkItemStore +namespace Microsoft.Qwiq.Integration.Tests { - internal IQueryFactory QueryFactory; + public abstract class WorkItemStoreTests : TimedContextSpecification + where T : IWorkItemStore + { + internal IQueryFactory QueryFactory; - protected T WorkItemStore; + protected T WorkItemStore; - public override void Cleanup() - { - WorkItemStore?.Dispose(); - base.Cleanup(); - } + public override void Cleanup() + { + WorkItemStore?.Dispose(); + base.Cleanup(); + } - public override void Given() - { - WorkItemStore = Create(); - } + public override void Given() + { + WorkItemStore = Create(); + } - protected abstract T Create(); -} -//} + protected abstract T Create(); + } +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemTests.cs b/test/Qwiq.Integration.Tests/WorkItemTests.cs index dc703add..c33ecc37 100644 --- a/test/Qwiq.Integration.Tests/WorkItemTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemTests.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Globalization; + +using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -10,7 +12,7 @@ public class Given_a_WorkItem_from_REST : RestWorkItemContextSpecification } [TestClass] - public class Given_a_WorkItem_from_SOAP : RestWorkItemContextSpecification + public class Given_a_WorkItem_from_SOAP : SoapWorkItemContextSpecification { } @@ -25,7 +27,7 @@ public abstract class WorkItemContextSpecification : WorkItemStoreTests [TestCategory("localOnly")] public void Reading_Id_from_Fields_property_with_ReferenceName_equals_the_property_value() { - Result.Fields[CoreFieldRefNames.Id]?.Value?.ToString().ShouldEqual(Result.Id.ToString()); + Result.Fields[CoreFieldRefNames.Id]?.Value?.ToString().ShouldEqual(Result.Id.ToString(CultureInfo.InvariantCulture)); } [TestMethod] diff --git a/test/Qwiq.Tests.Common/Clock.cs b/test/Qwiq.Tests.Common/Clock.cs index 396d9e0a..42ff1444 100644 --- a/test/Qwiq.Tests.Common/Clock.cs +++ b/test/Qwiq.Tests.Common/Clock.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; -namespace Microsoft.Qwiq.Core.Tests +namespace Microsoft.Qwiq.Tests.Common { public static class Clock { diff --git a/test/Qwiq.Tests.Common/TimedContextSpecification.cs b/test/Qwiq.Tests.Common/TimedContextSpecification.cs index f55dd949..f237dca9 100644 --- a/test/Qwiq.Tests.Common/TimedContextSpecification.cs +++ b/test/Qwiq.Tests.Common/TimedContextSpecification.cs @@ -2,14 +2,33 @@ using System.Collections.Generic; using System.Diagnostics; -using Microsoft.Qwiq.Tests.Common; - -namespace Microsoft.Qwiq.Core.Tests +namespace Microsoft.Qwiq.Tests.Common { public abstract class TimedContextSpecification : ContextSpecification { private readonly IDictionary _durations = new Dictionary(StringComparer.OrdinalIgnoreCase); + protected void TimedAction(Action action, string category, string userMessage) + { + var start = Clock.GetTimestamp(); + try + { + action(); + } + finally + { + var stop = Clock.GetTimestamp(); + var duration = Clock.GetTimeSpan(start, stop); + Debug.Print("{0}: {1} {2}", category, duration, userMessage); + if (!_durations.ContainsKey(category)) + { + _durations[category] = TimeSpan.Zero; + } + + _durations[category] += duration; + } + } + protected T TimedAction(Func action, string category, string userMessage) { var start = Clock.GetTimestamp(); @@ -35,7 +54,7 @@ public override void Cleanup() { foreach (var category in _durations) { - Debug.Print("{0}: {1}", category.Key, category.Value); + Debug.Print("{0}: {1} Total", category.Key, category.Value); } } } From 49c4c7d4f31ab6a22c0fcb1395e6c4615a680b20 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:06:45 -0700 Subject: [PATCH 113/251] TypeRestrictionFragment uses `CoreFieldRefNames.WorkItemType` constant Rather than hard coding a value in the predicate, the constant reference name is used. --- .../Fragments/TypeRestrictionFragment.cs | 2 +- test/Qwiq.Mapper.Tests/QueryBuilderTests.cs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs b/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs index 8a03d35d..13b65e60 100644 --- a/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs +++ b/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs @@ -17,7 +17,7 @@ public string Get(Type queryType) { var numberOfTypesGreaterThanOne = _workItemTypes.Count() > 1; - var format = numberOfTypesGreaterThanOne ? "([Work Item Type] IN ({0}))" : "([Work Item Type] = {0})"; + var format = numberOfTypesGreaterThanOne ? $"([{CoreFieldRefNames.WorkItemType}] IN ({{0}}))" : $"([{CoreFieldRefNames.WorkItemType}] = {{0}})"; var replacement = string.Join(", ", _workItemTypes.Select(t => "'" + t + "'")); return string.Format(format, replacement); } diff --git a/test/Qwiq.Mapper.Tests/QueryBuilderTests.cs b/test/Qwiq.Mapper.Tests/QueryBuilderTests.cs index 35a6beb4..34d537ba 100644 --- a/test/Qwiq.Mapper.Tests/QueryBuilderTests.cs +++ b/test/Qwiq.Mapper.Tests/QueryBuilderTests.cs @@ -29,12 +29,12 @@ protected override IFieldMapper CreateFieldMapper() [TestClass] // ReSharper disable once InconsistentNaming - public class when_a_query_is_on_a_field_that_is_nullable : GenericQueryBuilderTestsBase + public class when_a_query_is_on_a_field_that_is_nullable : GenericQueryBuilderTestsBase { public override void When() { base.When(); - Expected += " WHERE (([NullableField] = 1) AND ([Work Item Type] = 'MockWorkItem'))"; + Expected += $" WHERE (([NullableField] = 1) AND ([{CoreFieldRefNames.WorkItemType}] = 'MockWorkItem'))"; Actual = Query.Where(item => item.NullableField.Value == 1).ToString(); } @@ -47,7 +47,7 @@ public void the_value_of_the_field_is_compared() [TestClass] // ReSharper disable once InconsistentNaming - public class when_a_where_clause_filters_on_a_field_with_no_field_definition_attribute : GenericQueryBuilderTestsBase + public class when_a_where_clause_filters_on_a_field_with_no_field_definition_attribute : GenericQueryBuilderTestsBase { [TestMethod] [ExpectedException(typeof(ArgumentException))] @@ -60,12 +60,12 @@ public void an_argument_exception_is_thrown() [TestClass] // ReSharper disable once InconsistentNaming - public class when_a_select_clause_is_used : GenericQueryBuilderTestsBase + public class when_a_select_clause_is_used : GenericQueryBuilderTestsBase { public override void When() { base.When(); - Expected += " WHERE (([Work Item Type] = 'MockWorkItem'))"; + Expected += $" WHERE (([{CoreFieldRefNames.WorkItemType}] = 'MockWorkItem'))"; Actual = Query.Select(item => new { One = item.IntField, Two = item.IntField }).ToString(); } @@ -78,12 +78,12 @@ public void the_query_string_contains_all_fields_for_work_item() [TestClass] // ReSharper disable once InconsistentNaming - public class when_two_select_clauses_are_chained : GenericQueryBuilderTestsBase + public class when_two_select_clauses_are_chained : GenericQueryBuilderTestsBase { public override void When() { base.When(); - Expected += " WHERE (([Work Item Type] = 'MockWorkItem'))"; + Expected += $" WHERE (([{CoreFieldRefNames.WorkItemType}] = 'MockWorkItem'))"; Actual = Query.Select(item => new { One = item.IntField, Two = item.IntField }).Select(item2 => new { ABC = item2.Two }).ToString(); } @@ -119,7 +119,7 @@ public class when_a_query_is_against_a_type_with_multiple_workitemtype_attribute public override void When() { base.When(); - Expected += " WHERE (([IntField] > 1) AND ([Work Item Type] IN ('Baz', 'Buzz', 'Fizz')))"; + Expected += $" WHERE (([IntField] > 1) AND ([{CoreFieldRefNames.WorkItemType}] IN ('Baz', 'Buzz', 'Fizz')))"; Actual = Query.Where(item => item.IntField > 1).ToString(); } From 2ef4388bac1b2a0e9f62fd6e3b5aca014f202836 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:05:11 -0700 Subject: [PATCH 114/251] Update TypeRestrictionFragment to use HashSet Work item types are now kept in a HashSet to reduce duplication when emitting multiple types in WIQL. --- src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs b/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs index 13b65e60..5fd7a95f 100644 --- a/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs +++ b/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs @@ -6,16 +6,16 @@ namespace Microsoft.Qwiq.Linq.Fragments { internal class TypeRestrictionFragment : IFragment { - private readonly ICollection _workItemTypes; + private readonly HashSet _workItemTypes; - public TypeRestrictionFragment(ICollection workItemTypes) + public TypeRestrictionFragment(IEnumerable workItemTypes) { - _workItemTypes = workItemTypes; + _workItemTypes = new HashSet(workItemTypes, Comparer.OrdinalIgnoreCase); } public string Get(Type queryType) { - var numberOfTypesGreaterThanOne = _workItemTypes.Count() > 1; + var numberOfTypesGreaterThanOne = _workItemTypes.Count > 1; var format = numberOfTypesGreaterThanOne ? $"([{CoreFieldRefNames.WorkItemType}] IN ({{0}}))" : $"([{CoreFieldRefNames.WorkItemType}] = {{0}})"; var replacement = string.Join(", ", _workItemTypes.Select(t => "'" + t + "'")); From 4f4d5829e3a6d1f2bd9ce71c96577627ddcb3133 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:06:19 -0700 Subject: [PATCH 115/251] Correct mock model WIT Model specifies a WIT that does not exist in the system. Updating to use an existing WIT. --- test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs b/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs index b66f0b20..ef4bd734 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/SimpleMockModel.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Mapper.Tests.Mocks { - [WorkItemType("SimpleMockWorkItem")] + [WorkItemType("Task")] public class SimpleMockModel : IIdentifiable { [FieldDefinition(CoreFieldRefNames.Id)] From 4a826137aaac6c909b807083f5b7b26d107ca70c Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:33:34 -0700 Subject: [PATCH 116/251] Add integration tests for known field behavior differences Tests for Core fields are broken up into three types: - Core fields with no known differences - Core fields with identities - Core fields with date/time The core fields with no known differences make regular equality assertions between work items produced by each client. It is expected that there are no known differences. The core fields with identities may have differences between each client: SOAP will produce an identity in either the "Display Name" or "Display Name " depending on ambiguity. REST always produces the latter. Identities are pushed through a helper class (`IdentityFieldValue`) to parse out the different pieces and compare the resulting `DisplayName` for equality. It is expected that there are no known differences. The core fields with date/time have differences: SOAP converts the value to the offset provided in the user's preferences, while REST always sends the value as UTC. Each date value is returned and SOAP values are converted to UTC via the built-in method on the `DateTime` type for equality. It is expected that there are no known differences. --- ...rationContextSpecificationSpecification.cs | 97 ++++++++++++++++--- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/test/Qwiq.Integration.Tests/IntegrationContextSpecificationSpecification.cs b/test/Qwiq.Integration.Tests/IntegrationContextSpecificationSpecification.cs index 9139ead6..1efe73ab 100644 --- a/test/Qwiq.Integration.Tests/IntegrationContextSpecificationSpecification.cs +++ b/test/Qwiq.Integration.Tests/IntegrationContextSpecificationSpecification.cs @@ -36,19 +36,11 @@ public void AreaPath_is_equal() SoapResult.WorkItem[CoreFieldRefNames.AreaPath].ShouldEqual(SoapResult.WorkItem.AreaPath); } - public override void Cleanup() - { - SoapResult?.Dispose(); - RestResult?.Dispose(); - - base.Cleanup(); - } - [TestMethod] [TestCategory("localOnly")] [TestCategory("SOAP")] [TestCategory("REST")] - public void CoreFields_are_equal() + public void Core_identity_fields_contain_similar_information() { var exceptions = new List(); var identityFields = new[] @@ -56,21 +48,94 @@ public void CoreFields_are_equal() CoreFieldRefNames.AssignedTo, CoreFieldRefNames.AuthorizedAs, CoreFieldRefNames.ChangedBy, CoreFieldRefNames.CreatedBy }; - foreach (var field in CoreFieldRefNames.All) + + foreach (var field in identityFields) { try { - var restValue = RestResult.WorkItem[field]?.ToString(); var soapValue = SoapResult.WorkItem[field]?.ToString(); // If there is an identity field, drop the account name that REST returns to us - if (identityFields.Contains(field)) - { - restValue = new IdentityFieldValue(restValue).DisplayName; - soapValue = new IdentityFieldValue(soapValue).DisplayName; - } + restValue = new IdentityFieldValue(restValue).DisplayName; + soapValue = new IdentityFieldValue(soapValue).DisplayName; + + restValue.ShouldEqual(soapValue, field); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + if (exceptions.Any()) + { + throw new AggregateException(exceptions.EachToUsefulString(), exceptions); + } + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void Core_DateTime_fields_contain_similar_information() + { + var exceptions = new List(); + var dateTimeFields = new[] { CoreFieldRefNames.ChangedDate, CoreFieldRefNames.CreatedDate, }; + + foreach (var field in dateTimeFields) + { + try + { + var restValue = (DateTime?)RestResult.WorkItem[field]; + var soapValue = (DateTime)SoapResult.WorkItem[field]; + + restValue.GetValueOrDefault().ShouldEqual(soapValue.ToUniversalTime(), field); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + if (exceptions.Any()) + { + throw new AggregateException(exceptions.EachToUsefulString(), exceptions); + } + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void CoreFields_are_equal() + { + var exceptions = new List(); + + + var fieldsWithKnownDifferences = new[] + { + CoreFieldRefNames.AttachedFileCount, + CoreFieldRefNames.AuthorizedDate, + CoreFieldRefNames.BoardColumn, + CoreFieldRefNames.BoardLane, + CoreFieldRefNames.ChangedDate, + CoreFieldRefNames.CreatedDate, + CoreFieldRefNames.ExternalLinkCount, + CoreFieldRefNames.HyperlinkCount, + CoreFieldRefNames.RelatedLinkCount, + CoreFieldRefNames.AssignedTo, CoreFieldRefNames.AuthorizedAs, + CoreFieldRefNames.ChangedBy, CoreFieldRefNames.CreatedBy + }; + + foreach (var field in CoreFieldRefNames.All.Except(fieldsWithKnownDifferences)) + { + try + { + + var restValue = RestResult.WorkItem[field]?.ToString(); + var soapValue = SoapResult.WorkItem[field]?.ToString(); restValue.ShouldEqual(soapValue, field); From 83942871baf68f18f4b49d80d72bc1b04b44191d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:47:38 -0700 Subject: [PATCH 117/251] Upgrade C# Code Analysis packages --- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 80 ++++++++++-------- test/Qwiq.Benchmark/app.config | 38 ++++++++- test/Qwiq.Benchmark/packages.config | 82 ++++++++++--------- .../Qwiq.Identity.Benchmark.Tests.csproj | 80 ++++++++++-------- test/Qwiq.Identity.Benchmark.Tests/app.config | 38 ++++++++- .../packages.config | 82 ++++++++++--------- .../Qwiq.Mapper.Benchmark.Tests.csproj | 80 ++++++++++-------- test/Qwiq.Mapper.Benchmark.Tests/app.config | 38 ++++++++- .../packages.config | 82 ++++++++++--------- 9 files changed, 372 insertions(+), 228 deletions(-) diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 78dd6a40..9a6c84d5 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -56,11 +56,11 @@ ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll - - ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll @@ -73,63 +73,73 @@ - ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - - ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True - - ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - - ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - - ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll - - ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - - ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll - ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll - - ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - - ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll - - ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll - - ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll - - ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll diff --git a/test/Qwiq.Benchmark/app.config b/test/Qwiq.Benchmark/app.config index 4a32a14b..dbcd40db 100644 --- a/test/Qwiq.Benchmark/app.config +++ b/test/Qwiq.Benchmark/app.config @@ -10,6 +10,14 @@ + + + + + + + + @@ -44,7 +52,11 @@ - + + + + + @@ -54,6 +66,18 @@ + + + + + + + + + + + + @@ -86,10 +110,22 @@ + + + + + + + + + + + + diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index 2e37128d..474617c1 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -6,51 +6,53 @@ - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index eb62a13e..3a8cc080 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -48,11 +48,11 @@ ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll - - ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -62,65 +62,75 @@ - ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - - ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True - - ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - - ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - - ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll - - ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - - ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll - ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll - - ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - - ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll - - ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll - - ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll - - ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll diff --git a/test/Qwiq.Identity.Benchmark.Tests/app.config b/test/Qwiq.Identity.Benchmark.Tests/app.config index 112abb2a..06972b92 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/app.config +++ b/test/Qwiq.Identity.Benchmark.Tests/app.config @@ -6,6 +6,14 @@ + + + + + + + + @@ -24,12 +32,28 @@ - + + + + + + + + + + + + + + + + + @@ -42,6 +66,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/packages.config b/test/Qwiq.Identity.Benchmark.Tests/packages.config index ae4bbe0e..918b71f2 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/packages.config +++ b/test/Qwiq.Identity.Benchmark.Tests/packages.config @@ -4,50 +4,52 @@ - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index 347e23ba..831d18b0 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -47,11 +47,11 @@ ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll - - ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -67,67 +67,77 @@ - ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - - ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True - - ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - - ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - - ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll - - ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - - ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll - ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll - - ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - - ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll - - ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll - - ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll - - ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll diff --git a/test/Qwiq.Mapper.Benchmark.Tests/app.config b/test/Qwiq.Mapper.Benchmark.Tests/app.config index 0ac73ec8..f6d6a949 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/app.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/app.config @@ -12,7 +12,7 @@ - + @@ -26,6 +26,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 645a9525..8a9032d3 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -4,52 +4,54 @@ - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + \ No newline at end of file From 4635cbdb73f8c6f6e27e88650ad44495199d32a5 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:50:32 -0700 Subject: [PATCH 118/251] Remove `localOnly` tag from benchmark tests --- test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs | 1 - test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs | 1 - test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs index 6c1ab894..267383c1 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs +++ b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs @@ -30,7 +30,6 @@ public override void When() [TestMethod] [TestCategory(Constants.TestCategory.Benchmark)] [TestCategory(Constants.TestCategory.Performance)] - [TestCategory("localOnly")] public void Execute_Identity_Mapping_Performance_Benchmark() { // Intentionally left blank diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs index c0ade959..4bf89048 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs @@ -27,7 +27,6 @@ public override void When() [TestMethod] [TestCategory(Constants.TestCategory.Benchmark)] [TestCategory(Constants.TestCategory.Performance)] - [TestCategory("localOnly")] public void Execute_Mapping_Performance_Benchmark() { // Intentionally left blank diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs index ae16f237..aca3776e 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs @@ -26,7 +26,6 @@ public override void When() [TestMethod] [TestCategory(Constants.TestCategory.Benchmark)] [TestCategory(Constants.TestCategory.Performance)] - [TestCategory("localOnly")] public void Execute_Mapping_with_Links_Performance_Benchmark() { // Intentionally left blank From ae8d247e5a95ac62b4c462b63932be82d16f3ccd Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:52:22 -0700 Subject: [PATCH 119/251] Update test category filters Remove filters for `Benchmark` and `Performance` and add filters for `SOAP` and `REST`. This will lengthen a CI build as it will execute benchmarks. Estimated delay: 5-10 minutes. Statistics are printed in the output for the test, and artifacts are produced in the test output folder. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ac90f9cd..7def55b3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,8 +18,8 @@ test: categories: except: - localOnly - - Benchmark - - Performance + - SOAP + - REST artifacts: - path: '*.nupkg' name: NuGet From accd181d729883cc17fd6a8e36a655f931f4ec8e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 13:56:19 -0700 Subject: [PATCH 120/251] Qwiq.Benchmark.csproj: Remove unused references Remove references to Qwiq.Core and Qwiq.Mocks. Project only contains classes required for BenchmarkDotNet --- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 9a6c84d5..f258dbe9 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -128,7 +128,6 @@ ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll - ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll @@ -154,14 +153,6 @@ - - {8AC61B6E-BEC1-482D-A043-C65D2D343B35} - Qwiq.Core - - - {DB07E690-4B77-414F-91C7-1A48C9F01F24} - Qwiq.Mocks - {B45C92B0-AC36-409D-86A5-5428C87384C3} Qwiq.Tests.Common From 41b64276cfd38cf9ded121f0159efd98836b93f3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 14:52:53 -0700 Subject: [PATCH 121/251] Update BenchmarkConfig.cs Remove LegacyJit since we are running 4.7 with Ryu in production --- test/Qwiq.Benchmark/BenchmarkConfig.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/Qwiq.Benchmark/BenchmarkConfig.cs b/test/Qwiq.Benchmark/BenchmarkConfig.cs index 578405ba..c31cae19 100644 --- a/test/Qwiq.Benchmark/BenchmarkConfig.cs +++ b/test/Qwiq.Benchmark/BenchmarkConfig.cs @@ -3,6 +3,7 @@ using BenchmarkDotNet.Diagnostics.Windows; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Validators; namespace Microsoft.Qwiq.Benchmark { @@ -11,16 +12,19 @@ public class BenchmarkConfig : ManualConfig public BenchmarkConfig() { Add(Job.Clr.With(Jit.RyuJit).With(Platform.X64).With(new GcMode { Server = true })); - Add(Job.Clr.With(Jit.LegacyJit).With(Platform.X64).With(new GcMode { Server = true })); - Add(Job.Clr.With(Jit.RyuJit).With(Platform.X86).With(new GcMode { Server = true })); - Add(Job.Clr.With(Jit.LegacyJit).With(Platform.X86).With(new GcMode { Server = true })); + Add(Job.Clr.With(Jit.RyuJit).With(Platform.AnyCpu).With(new GcMode { Server = true })); + // GC and Memory Allocation Add(new BenchmarkDotNet.Diagnosers.MemoryDiagnoser()); Add(new InliningDiagnoser()); + // Checks whether any of the referenced assemblies is non-optimized + Add(JitOptimizationsValidator.FailOnError); + Add(StatisticColumn.AllStatistics); + } } } \ No newline at end of file From 321a2e7e315491e49619dec904be6928c3f4dbca Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 14:55:32 -0700 Subject: [PATCH 122/251] Update Poco benchmark Benchmark created LOTS of work items, which effectively began to test the CPU cache on the machine rather than the actual mapping code. This is now restricted to creating a single item and performing mapping against it. --- .../PocoMapping.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs index 4bf89048..202bda30 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System; using System.Collections.Generic; using System.Linq; @@ -7,10 +7,13 @@ using Microsoft.Qwiq.Benchmark; using Microsoft.Qwiq.Mapper.Attributes; +using Microsoft.Qwiq.Mapper.Benchmark.Tests; using Microsoft.Qwiq.Mocks; using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Should; + using B = Microsoft.Qwiq.Mapper.Benchmark.Tests.BENCHMARK_Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy; namespace Microsoft.Qwiq.Mapper.Benchmark.Tests @@ -38,6 +41,10 @@ public class Benchmark private WorkItemMapper _mapper; private IEnumerable _items; + private IEnumerable _item; + + private Type _type; + [Setup] public void SetupData() { @@ -49,14 +56,23 @@ public void SetupData() var wis = new MockWorkItemStore(); var generator = new WorkItemGenerator(() => wis.Create(), new[] { "Revisions", "Item" }); - _items = generator.Generate(); + _items = generator.Generate(1); wis.Add(_items); + + _item = new[] { _items.First() }; + _type = typeof(MockModel); + } + + [Benchmark(Baseline = true)] + public IEnumerable Generic() + { + return _mapper.Create(_item).ToList(); } [Benchmark] - public IList Execute() + public IEnumerable> NonGeneric() { - return _mapper.Create(_items).ToList(); + return _mapper.Create(_type, _item).ToList(); } } } @@ -69,6 +85,10 @@ public class Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy : ContextS { private B.Benchmark _benchmark; + private IEnumerable _genericResult; + + private IEnumerable> _nonGenericResult; + public override void Given() { _benchmark = new B.Benchmark(); @@ -77,13 +97,14 @@ public override void Given() public override void When() { - _benchmark.Execute(); + _genericResult = _benchmark.Generic(); + _nonGenericResult = _benchmark.NonGeneric(); } [TestMethod] public void Execute() { - Assert.Inconclusive("There is no condition verified. This executes to ensure the benchmark code functions without exception."); + _genericResult.ShouldContainOnly(_nonGenericResult); } } } From 324e355c4537870cdf61c4ee223d9524f2b5838e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 20 Apr 2017 15:36:02 -0700 Subject: [PATCH 123/251] Update PocoMapping test The test that wraps the benchmark test now verifies two mapped properties on each model: one with spaces, one without. --- test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs | 14 +++++++++----- .../Qwiq.Mapper.Benchmark.Tests.csproj | 3 +++ test/Qwiq.Mapper.Benchmark.Tests/packages.config | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs index 202bda30..8d8ef1bc 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs @@ -85,9 +85,9 @@ public class Given_a_set_of_WorkItems_with_an_AttributeMapperStrategy : ContextS { private B.Benchmark _benchmark; - private IEnumerable _genericResult; + private MockModel _genericResult; - private IEnumerable> _nonGenericResult; + private IIdentifiable _nonGenericResult; public override void Given() { @@ -97,14 +97,18 @@ public override void Given() public override void When() { - _genericResult = _benchmark.Generic(); - _nonGenericResult = _benchmark.NonGeneric(); + _genericResult = _benchmark.Generic().First(); + _nonGenericResult = _benchmark.NonGeneric().First(); } [TestMethod] public void Execute() { - _genericResult.ShouldContainOnly(_nonGenericResult); + var ng = (MockModel)_nonGenericResult; + var g = _genericResult; + + ng.WorkItemType.ShouldEqual(g.WorkItemType); + ng.Milestone.ShouldEqual(g.Milestone); } } } diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index 831d18b0..c41d9049 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -65,6 +65,9 @@ ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Should.1.1.20\lib\Should.dll + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 8a9032d3..2c0d1d49 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -10,6 +10,7 @@ + From f5735b8b6b161370234f4552db2f98b58adaca8d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 13:21:06 -0700 Subject: [PATCH 124/251] Add Wiql constants During clean up efforts, string literals will switch over to values contained in this constants file. --- src/Qwiq.Core/WiqlConstants.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/Qwiq.Core/WiqlConstants.cs diff --git a/src/Qwiq.Core/WiqlConstants.cs b/src/Qwiq.Core/WiqlConstants.cs new file mode 100644 index 00000000..baac00da --- /dev/null +++ b/src/Qwiq.Core/WiqlConstants.cs @@ -0,0 +1,19 @@ +namespace Microsoft.Qwiq +{ + + + public static class WiqlConstants + { + public const string DoesNotContain = "DoesNotContain"; + public const string MayContain = "MayContain"; + public const string MustContain = "MustContain"; + public const string Recursive = "Recursive"; + public const string ReturnMatchingChild = "ReturnMatchingChildren"; + public const string SourcePrefix = "Source"; + public const string TargetPrefix = "Target"; + public const string WorkItemLinkTable = "WorkItemLinks"; + public const string WorkItemTable = "WorkItems"; + } +} + + From 543c3a87203a78fea12f92f13b413c2000584c92 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 13:22:07 -0700 Subject: [PATCH 125/251] Add TFS connection factory The abstract class provides a foundation for creating TFS connections for SOAP and REST clients. --- src/Qwiq.Core/TfsConnectionFactory.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/Qwiq.Core/TfsConnectionFactory.cs diff --git a/src/Qwiq.Core/TfsConnectionFactory.cs b/src/Qwiq.Core/TfsConnectionFactory.cs new file mode 100644 index 00000000..40b46cae --- /dev/null +++ b/src/Qwiq.Core/TfsConnectionFactory.cs @@ -0,0 +1,14 @@ +using Microsoft.Qwiq.Credentials; + +namespace Microsoft.Qwiq +{ + public interface ITfsConnectionFactory + { + ITeamProjectCollection Create(AuthenticationOptions options); + } + + public abstract class TfsConnectionFactory : ITfsConnectionFactory + { + public abstract ITeamProjectCollection Create(AuthenticationOptions options); + } +} From 974ad240360595c1deba04194cc4772b23da2e74 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 13:22:43 -0700 Subject: [PATCH 126/251] Add WIQL and TfsConnection factory to Core project --- src/Qwiq.Core/Qwiq.Core.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 04c5e6b3..682754ed 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -204,8 +204,10 @@ + + From 04213ac8aff378e45f7c17eb6f9c0ee4f70cf6cd Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 13:24:16 -0700 Subject: [PATCH 127/251] Update WIS factories to use Tfs connection factory --- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 1 + src/Qwiq.Core.Rest/TfsConnectionFactory.cs | 72 +++++++++++++++++++ src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 55 +++----------- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 1 + src/Qwiq.Core.Soap/TfsConnectionFactory.cs | 70 ++++++++++++++++++ .../TfsTeamProjectCollection.cs | 2 +- src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 49 ++----------- .../IntegrationSettings.cs | 34 +++++---- .../WorkItemStoreFactoryTests.cs | 2 +- 9 files changed, 175 insertions(+), 111 deletions(-) create mode 100644 src/Qwiq.Core.Rest/TfsConnectionFactory.cs create mode 100644 src/Qwiq.Core.Soap/TfsConnectionFactory.cs diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index c122676d..a08d8593 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -111,6 +111,7 @@ + diff --git a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs new file mode 100644 index 00000000..b420b841 --- /dev/null +++ b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs @@ -0,0 +1,72 @@ +using System; + +using Microsoft.Qwiq.Credentials; +using Microsoft.Qwiq.Exceptions; +using Microsoft.TeamFoundation.Build.WebApi; +using Microsoft.VisualStudio.Services.Common; +using Microsoft.VisualStudio.Services.WebApi; + +namespace Microsoft.Qwiq.Rest +{ + public class TfsConnectionFactory : Qwiq.TfsConnectionFactory + { + public static readonly ITfsConnectionFactory Default = Nested.Instance; + + private TfsConnectionFactory() + { + } + + /// + public override ITeamProjectCollection Create(AuthenticationOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + var credentials = options.Credentials; + + foreach (var credential in credentials) + { + try + { + var tfsNative = ConnectToTfsCollection(options.Uri, credential); + var tfsProxy = + ExceptionHandlingDynamicProxyFactory + .Create(new VssConnectionAdapter(tfsNative)); + + options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfsProxy)); + + return tfsProxy; + } + catch (Exception e) + { + options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); + } + } + + var nocreds = new AccessDeniedException("Invalid credentials"); + options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); + throw nocreds; + } + + private static VssConnection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) + { + var tfsServer = new VssConnection(endpoint, credentials); + tfsServer.ConnectAsync(VssConnectMode.Automatic).GetAwaiter().GetResult(); + if (!tfsServer.HasAuthenticated) throw new InvalidOperationException("Could not connect."); + return tfsServer; + } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly TfsConnectionFactory Instance = new TfsConnectionFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index df754d6a..f60901c5 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -1,8 +1,6 @@ using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.Build.WebApi; -using Microsoft.VisualStudio.Services.Common; -using Microsoft.VisualStudio.Services.WebApi; + using System; using System.Collections.Generic; using System.Linq; @@ -11,7 +9,7 @@ namespace Microsoft.Qwiq.Rest { public class WorkItemStoreFactory : IWorkItemStoreFactory { - public static readonly IWorkItemStoreFactory Instance = Nested.Instance; + public static readonly IWorkItemStoreFactory Default = Nested.Instance; private WorkItemStoreFactory() { @@ -20,40 +18,11 @@ private WorkItemStoreFactory() public IWorkItemStore Create(AuthenticationOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); - var credentials = options.Credentials; - - foreach (var credential in credentials) - try - { - var tfsNative = ConnectToTfsCollection(options.Uri, credential); - var tfsProxy = - ExceptionHandlingDynamicProxyFactory.Create( - new VssConnectionAdapter(tfsNative)); - - options.Notifications.AuthenticationSuccess( - new AuthenticationSuccessNotification(credential, tfsProxy)); - - IWorkItemStore wis; - switch (options.ClientType) - { - case ClientType.Rest: - wis = CreateRestWorkItemStore(tfsProxy); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(options.ClientType)); - } + if (options.ClientType != ClientType.Rest) throw new InvalidOperationException(); - return ExceptionHandlingDynamicProxyFactory.Create(wis); - } - catch (Exception e) - { - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); - } - - var nocreds = new AccessDeniedException("Invalid credentials"); - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); - throw nocreds; + var tfsProxy = (IInternalTeamProjectCollection)TfsConnectionFactory.Default.Create(options); + var wis = CreateRestWorkItemStore(tfsProxy); + return ExceptionHandlingDynamicProxyFactory.Create(wis); } [Obsolete( @@ -77,19 +46,11 @@ public IWorkItemStore Create( } [Obsolete( - "This method is deprecated and will be removed in a future release. See property Instance instead.", + "This method is deprecated and will be removed in a future release. See property Default instead.", false)] public static IWorkItemStoreFactory GetInstance() { - return Instance; - } - - private static VssConnection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) - { - var tfsServer = new VssConnection(endpoint, credentials); - tfsServer.ConnectAsync(VssConnectMode.Automatic).GetAwaiter().GetResult(); - if (!tfsServer.HasAuthenticated) throw new InvalidOperationException("Could not connect."); - return tfsServer; + return Default; } private static IWorkItemStore CreateRestWorkItemStore(IInternalTeamProjectCollection tfs) diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 5030dc7e..0d71c270 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -227,6 +227,7 @@ + diff --git a/src/Qwiq.Core.Soap/TfsConnectionFactory.cs b/src/Qwiq.Core.Soap/TfsConnectionFactory.cs new file mode 100644 index 00000000..653cfbab --- /dev/null +++ b/src/Qwiq.Core.Soap/TfsConnectionFactory.cs @@ -0,0 +1,70 @@ +using System; + +using Microsoft.Qwiq.Credentials; +using Microsoft.Qwiq.Exceptions; +using Microsoft.TeamFoundation.Build.Client; +using Microsoft.VisualStudio.Services.Common; + +namespace Microsoft.Qwiq.Soap +{ + public class TfsConnectionFactory : Qwiq.TfsConnectionFactory + { + public static readonly ITfsConnectionFactory Default = Nested.Instance; + + private TfsConnectionFactory() + { + } + + /// + public override ITeamProjectCollection Create(AuthenticationOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + var credentials = options.Credentials; + + foreach (var credential in credentials) + { + try + { + var tfsNative = ConnectToTfsCollection(options.Uri, credential); + var tfsProxy = + ExceptionHandlingDynamicProxyFactory.Create( + new TfsTeamProjectCollection(tfsNative)); + + options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfsProxy)); + + return tfsProxy; + } + catch (Exception e) + { + options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); + } + } + + var nocreds = new AccessDeniedException("Invalid credentials"); + options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); + throw nocreds; + } + + private static TeamFoundation.Client.TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) + { + var tfsServer = new TeamFoundation.Client.TfsTeamProjectCollection(endpoint, credentials); + tfsServer.EnsureAuthenticated(); + return tfsServer; + } + + // ReSharper disable ClassNeverInstantiated.Local + private class Nested + // ReSharper restore ClassNeverInstantiated.Local + { + // ReSharper disable MemberHidesStaticFromOuterClass + internal static readonly TfsConnectionFactory Instance = new TfsConnectionFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Nested() + { + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs index 9619d946..8d5c61e9 100644 --- a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs @@ -9,7 +9,7 @@ namespace Microsoft.Qwiq.Soap { /// /// - /// + /// internal class TfsTeamProjectCollection : IInternalTeamProjectCollection { private readonly Lazy _css; diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 069ad735..e034810c 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -3,16 +3,12 @@ using System.Linq; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation; -using Microsoft.TeamFoundation.Build.Client; -using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Soap { public class WorkItemStoreFactory : IWorkItemStoreFactory { - public static readonly IWorkItemStoreFactory Instance = Nested.Instance; + public static readonly IWorkItemStoreFactory Default = Nested.Instance; private WorkItemStoreFactory() { @@ -21,36 +17,8 @@ private WorkItemStoreFactory() public IWorkItemStore Create(AuthenticationOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); - switch (options.ClientType) - { - case ClientType.Soap: - var credentials = options.Credentials; - foreach (var credential in credentials) - try - { - var tfsNative = ConnectToTfsCollection(options.Uri, credential); - var tfsProxy = - ExceptionHandlingDynamicProxyFactory.Create( - new TfsTeamProjectCollection(tfsNative)); - - options.Notifications.AuthenticationSuccess( - new AuthenticationSuccessNotification(credential, tfsProxy)); - return CreateSoapWorkItemStore(tfsProxy); - } - catch (TeamFoundationServerUnauthorizedException e) - { - options.Notifications.AuthenticationFailed( - new AuthenticationFailedNotification(credential, e)); - } - var nocreds = new AccessDeniedException("Invalid credentials"); - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); - throw nocreds; - case ClientType.Rest: - return Rest.WorkItemStoreFactory.Instance.Create(options); - - default: - throw new ArgumentOutOfRangeException(nameof(options.ClientType)); - } + var tfsProxy = (IInternalTeamProjectCollection)TfsConnectionFactory.Default.Create(options); + return CreateSoapWorkItemStore(tfsProxy); } [Obsolete( @@ -74,18 +42,11 @@ public IWorkItemStore Create( } [Obsolete( - "This method is deprecated and will be removed in a future release. See property Instance instead.", + "This method is deprecated and will be removed in a future release. See property Default instead.", false)] public static IWorkItemStoreFactory GetInstance() { - return Instance; - } - - private static TeamFoundation.Client.TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) - { - var tfsServer = new TeamFoundation.Client.TfsTeamProjectCollection(endpoint, credentials); - tfsServer.EnsureAuthenticated(); - return tfsServer; + return Default; } private static IWorkItemStore CreateSoapWorkItemStore(IInternalTeamProjectCollection tfs) diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index 519ff039..7f2a59fc 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -7,26 +7,24 @@ namespace Microsoft.Qwiq.Integration.Tests { public static class IntegrationSettings { - public static Func CreateSoapStore { get; } = () => - { - var options = - new AuthenticationOptions( - Uri, - AuthenticationTypes.Windows, - ClientType.Soap); - return WorkItemStoreFactory.Instance.Create(options); - }; - private static readonly Uri Uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); public static Func CreateRestStore { get; } = () => - { - var options = - new AuthenticationOptions( - Uri, - AuthenticationTypes.Windows, - ClientType.Rest); - return WorkItemStoreFactory.Instance.Create(options); - }; + { + var options = RestOptions; + return WorkItemStoreFactory.Default.Create(options); + }; + + public static Func CreateSoapStore { get; } = () => + { + var options = SoapOptions; + return WorkItemStoreFactory.Default.Create(options); + }; + + public static AuthenticationOptions RestOptions { get; } = + new AuthenticationOptions(Uri, AuthenticationTypes.Windows, ClientType.Rest); + + public static AuthenticationOptions SoapOptions { get; } = + new AuthenticationOptions(Uri, AuthenticationTypes.Windows, ClientType.Soap); } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs index b687d94d..0db2c62b 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStoreFactoryTests.cs @@ -19,7 +19,7 @@ public abstract class WorkItemStoreFactoryContextSpecification : TimedContextSpe public override void Given() { - Instance = WorkItemStoreFactory.Instance; + Instance = WorkItemStoreFactory.Default; WorkItemStore = TimedAction(Create, "SOAP", "WIS Create"); base.Given(); } From b14974ec46b28ee84694045c02e6b2c20ed6dbcc Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 15:06:20 -0700 Subject: [PATCH 128/251] Remove deprecated method on IdentityVisitor --- src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs b/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs index 3b5dc74b..f7bd69ec 100644 --- a/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs +++ b/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs @@ -16,12 +16,6 @@ public IdentityVisitor(IIdentityMapper mapper) _mapper = mapper; } - [Obsolete("Use the overload which takes an IIdentityMapper.")] - public IdentityVisitor(IIdentityManagementService identityManagementService, string tenantId, - params string[] domains) : this(new IdentityMapper(identityManagementService, tenantId, domains)) - { - } - protected override Expression VisitBinary(BinaryExpression node) { _needsIdentityMapping = NeedsIdentityMapping(new[] { node.Left, node.Right }); From 67829af4e61b2c6e1afafba20581674ed5b51e1d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 15:09:26 -0700 Subject: [PATCH 129/251] Unit tests for IdentityFieldValue --- src/Qwiq.Core/IdentityFieldValue.cs | 160 +++++++++++-- .../IdentityFieldValueTests.cs | 219 ++++++++++++++++++ test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 1 + 3 files changed, 359 insertions(+), 21 deletions(-) create mode 100644 test/Qwiq.Core.Tests/IdentityFieldValueTests.cs diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index e582656e..115e93a5 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -1,14 +1,37 @@ -using Microsoft.VisualStudio.Services.Common; -using System; +using System; +using System.Diagnostics; using System.Globalization; +using System.Text.RegularExpressions; + +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq { /// /// Class IdentityFieldValue. /// + [DebuggerDisplay("{" + nameof(DisplayName) + "}")] public class IdentityFieldValue { + // "Chris Johnson " + private static readonly Regex AccountNameRegex = new Regex("^.+<(.+@.+)>$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + // "Chris Johnson" + private static readonly Regex DisplayNameRegex = new Regex( + @"^[^<\\]*(?:<[^>]*)?$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + // "Chris Johnson " + private static readonly Regex DomainAccountRegex = new Regex(@"^.+<(.+\\.+)>$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex ScopeRegex = new Regex( + @"^\[[0-9A-Za-z ]+\]\\(.+)<([0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12})>$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex VsidRegex = new Regex( + @"^\[[0-9A-Za-z ]+\]\\(.+)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + /// /// Initializes a new instance of the class. /// @@ -27,21 +50,25 @@ public IdentityFieldValue(ITeamFoundationIdentity identity) /// The value from the descriptor identifier (e.g. /// CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C\chrisjohns@contoso.com). /// - /// The security identifier (SID) for the identity. - public IdentityFieldValue(string displayName, string fullName, string sid) + /// The security identifier (SID) for the identity. + public IdentityFieldValue(string displayName, string fullName, string teamFoundationId) : this(displayName) { - Sid = sid; FullName = fullName; + if (!string.IsNullOrEmpty(teamFoundationId)) + { + if (Guid.TryParse(teamFoundationId, out Guid tfsid)) TeamFoundationId = teamFoundationId; + } + var arr = FullName.Split(IdentityConstants.DomainAccountNameSeparator); - if (arr.Length != 2 || arr[1] == Sid) return; + if (arr.Length != 2 || arr[1] == TeamFoundationId) return; if (arr[1].Contains("@")) { Email = arr[1]; Alias = arr[1].Split('@')[0]; - + AccountName = arr[1]; if (Guid.TryParse(arr[0], out Guid guid)) Domain = arr[0]; } else @@ -60,20 +87,49 @@ public IdentityFieldValue(string displayName, string fullName, string sid) public IdentityFieldValue(string displayName) { + DisplayPart = displayName; + if (!string.IsNullOrEmpty(displayName)) - if (displayName.Contains("<")) + { + if (TryGetVsid(displayName, out Guid guid2, out string str)) { - var arr = displayName.Split('<'); - if (arr[1].Contains("@")) + DisplayPart = str; + return; + } + if (TryGetDomainAndAccountName(displayName, out string str2)) + { + var strArray = str2.Split(IdentityConstants.DomainAccountNameSeparator); + if (strArray.Length != 2) { - Email = arr[1].Trim('>'); - Alias = Email.Split('@')[0]; + DisplayPart = displayName; + return; } - } - DisplayPart = displayName; + Domain = strArray[0]; + Alias = strArray[1]; + DisplayPart = displayName; + return; + } + if (TryGetAccountName(displayName, out str2)) + { + AccountName = str2; + if (str2.Contains("@")) + { + Email = str2; + Alias = str2.Split('@')[0]; + } + DisplayPart = displayName; + return; + } + if (TryGetDisplayName(displayName, out str2)) + { + DisplayPart = str2; + } + } } + public string AccountName { get; } + /// /// Gets the alias. /// @@ -122,17 +178,79 @@ public string IdentityName get { if (!string.IsNullOrEmpty(Email)) return Email; - if (!string.IsNullOrEmpty(Domain)) return string.Format(CultureInfo.InvariantCulture, IdentityConstants.DomainQualifiedAccountNameFormat, Domain, Alias); + if (!string.IsNullOrEmpty(Domain)) + return string.Format(CultureInfo.InvariantCulture, IdentityConstants.DomainQualifiedAccountNameFormat, Domain, Alias); if (!string.IsNullOrEmpty(Alias)) return Alias; - return string.Empty; + return null; } } - /// - /// Gets the Security Identifier (SID). - /// - /// The SID. - public string Sid { get; } + + public string TeamFoundationId { get; } + + private static bool TryGetAccountName(string search, out string acccountName) + { + var match = AccountNameRegex.Match(search); + acccountName = null; + if (match.Success && match.Groups.Count > 1) + { + acccountName = match.Groups[1].Value; + return true; + } + return false; + } + + private static bool TryGetDisplayName(string search, out string displayName) + { + var match = DisplayNameRegex.Match(search); + displayName = null; + if (match.Success && match.Groups.Count > 0) + { + displayName = match.Groups[0].Value; + return true; + } + return false; + } + + private static bool TryGetDomainAndAccountName(string search, out string domainAndAcccountName) + { + var match = DomainAccountRegex.Match(search); + domainAndAcccountName = null; + if (match.Success && match.Groups.Count > 1 && match.Groups[1].Value.Contains(@"\")) + { + domainAndAcccountName = match.Groups[1].Value; + return true; + } + return false; + } + + private static bool TryGetScope(string search, out Guid scopeId, out string displayName) + { + var match = ScopeRegex.Match(search); + if (match.Success && match.Groups.Count > 1) + { + displayName = match.Groups[1].Value; + Guid.TryParse(match.Groups[2].Value, out scopeId); + return true; + } + scopeId = Guid.Empty; + displayName = string.Empty; + return false; + } + + private static bool TryGetVsid(string search, out Guid vsid, out string displayName) + { + var match = VsidRegex.Match(search); + if (match.Success && match.Groups.Count > 1) + { + displayName = match.Groups[1].Value; + Guid.TryParse(match.Groups[2].Value, out vsid); + return true; + } + vsid = Guid.Empty; + displayName = string.Empty; + return false; + } } } \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs b/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs new file mode 100644 index 00000000..047b6ae5 --- /dev/null +++ b/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs @@ -0,0 +1,219 @@ +using Microsoft.Qwiq.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Core.Tests +{ + public abstract class IdentityFieldValueContextSpecification : ContextSpecification + { + protected string DisplayName; + + protected string FullName; + protected IdentityFieldValue Result; + } + + [TestClass] + public class Given_an_identity_with_only_a_display_name : IdentityFieldValueContextSpecification + { + /// + public override void Given() + { + DisplayName = "Chris Johnson"; + } + + /// + public override void When() + { + Result = new IdentityFieldValue(DisplayName); + } + + [TestMethod] + public void TeamFoundationId_is_null() + { + Result.TeamFoundationId.ShouldBeNull(); + } + + [TestMethod] + public void Alias_is_null() + { + Result.Alias.ShouldBeNull(); + } + + [TestMethod] + public void AccountName_is_null() + { + Result.AccountName.ShouldBeNull(); + } + + [TestMethod] + public void DisplayName_is_FirstLast() + { + Result.DisplayName.ShouldEqual(DisplayName); + } + + [TestMethod] + public void Domain_is_null() + { + Result.Domain.ShouldBeNull(); + } + + [TestMethod] + public void Email_is_null() + { + Result.Email.ShouldBeNull(); + } + + [TestMethod] + public void FullName_is_null() + { + Result.FullName.ShouldBeNull(); + } + + [TestMethod] + public void IdentityName_is_empty() + { + Result.IdentityName.ShouldEqual(string.Empty); + } + } + + [TestClass] + public class Given_an_identity_with_a_combo_string : IdentityFieldValueContextSpecification + { + /// + public override void Given() + { + DisplayName = "Chris Johnson "; + } + + /// + public override void When() + { + Result = new IdentityFieldValue(DisplayName); + } + + [TestMethod] + public void TeamFoundationId_is_null() + { + Result.TeamFoundationId.ShouldBeNull(); + } + + [TestMethod] + public void Alias_is_null() + { + Result.Alias.ShouldEqual("chrisjohns"); + } + + [TestMethod] + public void AccountName_is_null() + { + Result.AccountName.ShouldEqual("chrisjohns@contoso.com"); + } + + [TestMethod] + public void DisplayName_is_FirstLast() + { + Result.DisplayName.ShouldEqual("Chris Johnson"); + } + + [TestMethod] + public void Domain_is_null() + { + Result.Domain.ShouldBeNull(); + } + + [TestMethod] + public void Email_is_null() + { + Result.Email.ShouldEqual("chrisjohns@contoso.com"); + } + + [TestMethod] + public void FullName_is_null() + { + Result.FullName.ShouldBeNull(); + } + + [TestMethod] + public void IdentityName_is_empty() + { + Result.IdentityName.ShouldEqual("chrisjohns@contoso.com"); + } + } + + [TestClass] + public class Given_an_identity_with_a_combo_string_and_fullname : IdentityFieldValueContextSpecification + { + /// + public override void Given() + { + DisplayName = "Chris Johnson "; + FullName = "CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C\\chrisjohns@contoso.com"; + } + + /// + public override void When() + { + Result = new IdentityFieldValue(DisplayName, FullName, null); + } + + [TestMethod] + public void TeamFoundationId_is_null() + { + Result.TeamFoundationId.ShouldBeNull(); + } + + [TestMethod] + public void Alias_is_null() + { + Result.Alias.ShouldEqual("chrisjohns"); + } + + [TestMethod] + public void AccountName_is_null() + { + Result.AccountName.ShouldEqual("chrisjohns@contoso.com"); + } + + [TestMethod] + public void DisplayName_is_FirstLast() + { + Result.DisplayName.ShouldEqual("Chris Johnson"); + } + + [TestMethod] + public void Domain_is_null() + { + Result.Domain.ShouldEqual("CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C"); + } + + [TestMethod] + public void Email_is_null() + { + Result.Email.ShouldEqual("chrisjohns@contoso.com"); + } + + [TestMethod] + public void FullName_is_null() + { + Result.FullName.ShouldEqual("CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C\\chrisjohns@contoso.com"); + } + + [TestMethod] + public void IdentityName_is_empty() + { + Result.IdentityName.ShouldEqual("chrisjohns@contoso.com"); + } + } + + [TestClass] + public class Given_an_identity_with_a_display_name_and_fullname : Given_an_identity_with_a_combo_string_and_fullname + { + /// + public override void Given() + { + base.Given(); + DisplayName = "Chris Johnson"; + } + } +} diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 1dd883bb..713f1dce 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -217,6 +217,7 @@ + From c54f8a424dad88cd7eacca0b2265050f36ee4c03 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 19:15:09 -0700 Subject: [PATCH 130/251] Code clean up --- src/Qwiq.Core/WiqlConstants.cs | 14 +++++++++----- src/Qwiq.Linq/Fragments/SelectFragment.cs | 4 +++- src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs | 4 +++- src/Qwiq.Linq/TranslatedQuery.cs | 4 ++-- src/Qwiq.Linq/WiqlTranslator.cs | 3 +-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Qwiq.Core/WiqlConstants.cs b/src/Qwiq.Core/WiqlConstants.cs index baac00da..b62d61d5 100644 --- a/src/Qwiq.Core/WiqlConstants.cs +++ b/src/Qwiq.Core/WiqlConstants.cs @@ -1,19 +1,23 @@ namespace Microsoft.Qwiq { - - public static class WiqlConstants { public const string DoesNotContain = "DoesNotContain"; + public const string MayContain = "MayContain"; + public const string MustContain = "MustContain"; + public const string Recursive = "Recursive"; + public const string ReturnMatchingChild = "ReturnMatchingChildren"; + public const string SourcePrefix = "Source"; + public const string TargetPrefix = "Target"; + public const string WorkItemLinkTable = "WorkItemLinks"; + public const string WorkItemTable = "WorkItems"; } -} - - +} \ No newline at end of file diff --git a/src/Qwiq.Linq/Fragments/SelectFragment.cs b/src/Qwiq.Linq/Fragments/SelectFragment.cs index 4b946964..151dcae5 100644 --- a/src/Qwiq.Linq/Fragments/SelectFragment.cs +++ b/src/Qwiq.Linq/Fragments/SelectFragment.cs @@ -9,12 +9,14 @@ internal class SelectFragment : IFragment public SelectFragment(ICollection fields) { + if (fields == null) throw new ArgumentNullException(nameof(fields)); + if (fields.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(fields)); _fields = fields; } public string Get(Type queryType) { - return "SELECT " + string.Join(", ", _fields) + " FROM WorkItems"; + return $"SELECT {string.Join(", ", _fields)} FROM {WiqlConstants.WorkItemTable}"; } public bool IsValid() diff --git a/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs b/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs index 5fd7a95f..7f955e72 100644 --- a/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs +++ b/src/Qwiq.Linq/Fragments/TypeRestrictionFragment.cs @@ -17,7 +17,9 @@ public string Get(Type queryType) { var numberOfTypesGreaterThanOne = _workItemTypes.Count > 1; - var format = numberOfTypesGreaterThanOne ? $"([{CoreFieldRefNames.WorkItemType}] IN ({{0}}))" : $"([{CoreFieldRefNames.WorkItemType}] = {{0}})"; + var format = numberOfTypesGreaterThanOne + ? $"([{CoreFieldRefNames.WorkItemType}] IN ({{0}}))" + : $"([{CoreFieldRefNames.WorkItemType}] = {{0}})"; var replacement = string.Join(", ", _workItemTypes.Select(t => "'" + t + "'")); return string.Format(format, replacement); } diff --git a/src/Qwiq.Linq/TranslatedQuery.cs b/src/Qwiq.Linq/TranslatedQuery.cs index 5390cc52..5cf886c0 100644 --- a/src/Qwiq.Linq/TranslatedQuery.cs +++ b/src/Qwiq.Linq/TranslatedQuery.cs @@ -52,7 +52,7 @@ private string BuildWhereString() if (WhereClauses.Count > 0) { result += " WHERE ("; - result += String.Join(" AND ", WhereClauses.Select(wc => wc.Get(UnderlyingQueryType))); + result += string.Join(" AND ", WhereClauses.Select(wc => wc.Get(UnderlyingQueryType))); result += ")"; } @@ -66,7 +66,7 @@ private string BuildOrderString() if (ThenOrderClauses.Count > 0) { result += " ORDER BY "; - result += String.Join(", ", ThenOrderClauses.Select(toc => toc.Get(UnderlyingQueryType))); + result += string.Join(", ", ThenOrderClauses.Select(toc => toc.Get(UnderlyingQueryType))); } return result; diff --git a/src/Qwiq.Linq/WiqlTranslator.cs b/src/Qwiq.Linq/WiqlTranslator.cs index ce71025e..ec293959 100644 --- a/src/Qwiq.Linq/WiqlTranslator.cs +++ b/src/Qwiq.Linq/WiqlTranslator.cs @@ -273,8 +273,7 @@ protected override Expression VisitBinary(BinaryExpression node) protected override Expression VisitConstant(ConstantExpression node) { - var queryable = node.Value as IQueryable; - if (queryable != null) + if (node.Value is IQueryable) { // Assume constant nodes with IQueryables are table references _expressionInProgress = new Queue(); From 4a36df20607d4632f2674c85772c8a25c03d148b Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 18:33:49 -0700 Subject: [PATCH 131/251] Use CachingFieldMapper as default mapper on WiqlTranslator --- src/Qwiq.Linq/WiqlTranslator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Linq/WiqlTranslator.cs b/src/Qwiq.Linq/WiqlTranslator.cs index ec293959..0bb715bf 100644 --- a/src/Qwiq.Linq/WiqlTranslator.cs +++ b/src/Qwiq.Linq/WiqlTranslator.cs @@ -21,7 +21,8 @@ public class WiqlTranslator : IWiqlTranslator { protected readonly IFieldMapper FieldMapper; - public WiqlTranslator() : this(new SimpleFieldMapper()) + public WiqlTranslator() + : this(new CachingFieldMapper(new SimpleFieldMapper())) { } @@ -293,7 +294,7 @@ protected override Expression VisitConstant(ConstantExpression node) _expressionInProgress.Enqueue(new DateTimeFragment(((DateTime)node.Value))); break; case "System.Int32[]": - var originalIntVals = ((int[])node.Value); + var originalIntVals = (int[])node.Value; _expressionInProgress.Enqueue(new NumberListFragment(originalIntVals)); break; case "System.String[]": From 677b7057cbc862397938b6476c342cced7a448aa Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 18:55:09 -0700 Subject: [PATCH 132/251] Add IdentityVisitor to verify identity values during tree evaluation --- src/Qwiq.Linq/Qwiq.Linq.csproj | 1 + src/Qwiq.Linq/Visitors/IdentityVisitor.cs | 78 +++++++++++++++++++ src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs | 39 ++++++++-- .../IdentityFieldValueTests.cs | 2 +- 4 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/Qwiq.Linq/Visitors/IdentityVisitor.cs diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index 6820da91..b7417e90 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Qwiq.Linq/Visitors/IdentityVisitor.cs b/src/Qwiq.Linq/Visitors/IdentityVisitor.cs new file mode 100644 index 00000000..eefbd467 --- /dev/null +++ b/src/Qwiq.Linq/Visitors/IdentityVisitor.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; + +namespace Microsoft.Qwiq.Linq.Visitors +{ + public class IdentityVisitor : ExpressionVisitor + { + private static readonly HashSet IdentityProperties; + + private bool _needsIdentityMapping; + + static IdentityVisitor() + { + IdentityProperties = + new HashSet(Comparer.OrdinalIgnoreCase) + { + "AssignedTo", + CoreFieldRefNames.AssignedTo, + CoreFieldRefNames.NameLookup[CoreFieldRefNames.AssignedTo], + "ChangedBy", + CoreFieldRefNames.ChangedBy, + CoreFieldRefNames.NameLookup[CoreFieldRefNames.ChangedBy], + "CreatedBy", + CoreFieldRefNames.CreatedBy, + CoreFieldRefNames.NameLookup[CoreFieldRefNames.CreatedBy] + }; + } + + protected override Expression VisitBinary(BinaryExpression node) + { + _needsIdentityMapping = NeedsIdentityMapping(new[] { node.Left, node.Right }); + + var newNode = base.VisitBinary(node); + _needsIdentityMapping = false; + + return newNode; + } + + protected override Expression VisitConstant(ConstantExpression node) + { + if (!_needsIdentityMapping) return base.VisitConstant(node); + + void Validate(string identity) + { + var i = new IdentityFieldValue(identity); + if (string.IsNullOrEmpty(i.AccountName)) + Trace.TraceWarning( + "'{0}' is not a combo-string and may be ambiguous. Use the combo-string to unambiguously refer to an identity.", + identity); + } + + var v = node.Value; + if (v is string s) Validate(s); + else if (v is IEnumerable sv) foreach (var s1 in sv) Validate(s1); + else Trace.TraceWarning("Unrecognized value in identity field. Type: {0}, Value: {1}", node.Type, node.Value); + + return base.VisitConstant(node); + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + _needsIdentityMapping = NeedsIdentityMapping(node.Arguments); + + // TODO: Support cases: item["Assigned To"] and item.Fields["Assigned To"] + var newNode = base.VisitMethodCall(node); + _needsIdentityMapping = false; + + return newNode; + } + + private static bool NeedsIdentityMapping(IEnumerable expressions) + { + return expressions.OfType().Any(arg => IdentityProperties.Contains(arg.Member.Name)); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs b/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs index 08a453c9..1660548d 100644 --- a/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs +++ b/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using System.Linq.Expressions; namespace Microsoft.Qwiq.Linq.Visitors @@ -5,23 +7,44 @@ namespace Microsoft.Qwiq.Linq.Visitors public class WiqlQueryBuilder : IWiqlQueryBuilder { private readonly IWiqlTranslator _translator; + private readonly ExpressionVisitor[] _visitors; + /// + /// Initializes a new instance of the class. + /// + /// The translator used to visit the tree. + public WiqlQueryBuilder(IWiqlTranslator translator) + : this(translator, new PartialEvaluator(), new IdentityVisitor(), new QueryRewriter()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The translator used to visit the tree. + /// The visitors used to visit the during . public WiqlQueryBuilder(IWiqlTranslator translator, params ExpressionVisitor[] visitors) { - _translator = translator; - _visitors = visitors; + _translator = translator ?? throw new ArgumentNullException(nameof(translator)); + _visitors = visitors ?? throw new ArgumentNullException(nameof(visitors)); + } + + /// + /// Initializes a new instance of the class with a default instance of + /// . + /// + public WiqlQueryBuilder() + : this(new WiqlTranslator()) + { } public TranslatedQuery BuildQuery(Expression expression) { - foreach (var visitor in _visitors) - { - expression = visitor.Visit(expression); - } + // Each visitor fist the expression + expression = _visitors.Aggregate(expression, (current, visitor) => visitor.Visit(current)); return _translator.Translate(expression); } } -} - +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs b/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs index 047b6ae5..26ea6415 100644 --- a/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs +++ b/test/Qwiq.Core.Tests/IdentityFieldValueTests.cs @@ -73,7 +73,7 @@ public void FullName_is_null() [TestMethod] public void IdentityName_is_empty() { - Result.IdentityName.ShouldEqual(string.Empty); + Result.IdentityName.ShouldBeNull(); } } From 2d0137b125021b961f228c4580dc47459b67e7e3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 18:56:49 -0700 Subject: [PATCH 133/251] Add unit tests to increase coverage of LINQ --- test/Qwiq.Linq.Tests/QueryBuilderTests.cs | 221 +++++++++++++++++++++- 1 file changed, 216 insertions(+), 5 deletions(-) diff --git a/test/Qwiq.Linq.Tests/QueryBuilderTests.cs b/test/Qwiq.Linq.Tests/QueryBuilderTests.cs index ce9481ba..474cf7f4 100644 --- a/test/Qwiq.Linq.Tests/QueryBuilderTests.cs +++ b/test/Qwiq.Linq.Tests/QueryBuilderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Microsoft.Qwiq.Linq.Visitors; @@ -7,6 +8,7 @@ using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; +using Should.Core.Exceptions; namespace Microsoft.Qwiq.Linq.Tests { @@ -16,12 +18,16 @@ public abstract class QueryBuilderTests : ContextSpecification protected string Actual; protected IOrderedQueryable Query; + protected WiqlQueryBuilder _builder; + + protected TeamFoundationServerWorkItemQueryProvider _queryProvider; + public override void Given() { base.Given(); - var builder = new WiqlQueryBuilder(new WiqlTranslator(), new PartialEvaluator(), new QueryRewriter()); - var queryProvider = new TeamFoundationServerWorkItemQueryProvider(new MockWorkItemStore(), builder); - Query = new Query(queryProvider, builder); + _builder = new WiqlQueryBuilder(); + _queryProvider = new TeamFoundationServerWorkItemQueryProvider(new MockWorkItemStore(), _builder); + Query = new Query(_queryProvider, _builder); } } @@ -130,7 +136,7 @@ public void it_is_translated_to_a_where_with_an_or_operator() [TestClass] // ReSharper disable once InconsistentNaming - public class when_a_query_has_a_field_that_should_be_in_a_list_of_values : QueryBuilderTests + public class when_a_query_has_a_field_that_should_be_in_a_list_of_string_array_values : QueryBuilderTests { private string[] _values; @@ -154,6 +160,76 @@ public void it_is_translated_to_an_in_operator() } } + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_a_query_has_a_field_that_should_be_in_a_list_of_IEnumerable_string_values : QueryBuilderTests + { + private IEnumerable _values; + + public override void Given() + { + _values = new[] { "person1", "person2" }.AsEnumerable(); + base.Given(); + } + + public override void When() + { + base.When(); + Expected = "SELECT * FROM WorkItems WHERE (([Keywords] IN ('person1', 'person2')))"; + Actual = Query.Where(item => _values.Contains(item.Keywords)).ToString(); + } + + [TestMethod] + public void it_is_translated_to_an_in_operator() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_a_query_has_a_field_that_should_be_in_a_list_of_Collection_string_values : QueryBuilderTests + { + private Collection _values; + + public override void Given() + { + _values = new Collection { "person1", "person2" }; + base.Given(); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void it_is_not_supported() + { + Actual = Query.Where(item => _values.Contains(item.Keywords)).ToString(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_a_query_has_a_field_that_should_be_in_a_list_of_HashSet_string_values : QueryBuilderTests + { + private HashSet _values; + + public override void Given() + { + _values = new HashSet + { + "person1", + "person2" + }; + base.Given(); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void it_is_not_supported() + { + Actual = Query.Where(item => _values.Contains(item.Keywords)).ToString(); + } + } + [TestClass] // ReSharper disable once InconsistentNaming @@ -304,7 +380,7 @@ public class when_a_where_clause_contains_a_ToUpper_call_on_a_string : QueryBuil public void a_NotSupportedException_is_thrown_to_notify_the_developer_that_text_matches_are_case_insensitive() { // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - Query.Where(item => item.Title.ToUpper() == "TEST").ToString(); + Actual = Query.Where(item => item.Title.ToUpper() == "TEST").ToString(); } } @@ -416,4 +492,139 @@ public void the_index_name_is_used_as_a_field_name() Actual.ShouldEqual(Expected); } } + + [TestClass] + public class Given_a_query_with_a_where_clause_with_an_enum : QueryBuilderTests + { + enum Sample { One, Two, Three} + + interface IWorkItem2 : IWorkItem + { + Sample EnumProperty { get; } + } + + private new IOrderedQueryable Query; + + /// + public override void Given() + { + base.Given(); + Query = new Query(_queryProvider, _builder); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void the_enum_is_translated() + { + Actual = Query.Where(item => item.EnumProperty == Sample.Three).ToString(); + } + } + + [TestClass] + public class Given_a_query_with_a_single_variable : QueryBuilderTests + { + public override void When() + { + base.When(); + Expected = "SELECT * FROM WorkItems WHERE (([Id] = 1))"; + int id = 1; + Actual = Query.Where(item => item.Id == id).ToString(); + } + + [TestMethod] + public void the_variable_value_is_written_to_the_wiql() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class Given_a_query_with_a_where_clause_on_a_known_identity_property_with_a_combo_value : QueryBuilderTests + { + public override void When() + { + base.When(); + Expected = "SELECT * FROM WorkItems WHERE (([Assigned To] = 'Dan Jump '))"; + Actual = Query.Where(item => item.AssignedTo == "Dan Jump ").ToString(); + } + + [TestMethod] + public void the_value_is_written_to_WIQL() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class Given_a_query_with_a_where_clause_on_a_known_identity_property_with_an_alias : QueryBuilderTests + { + public override void When() + { + base.When(); + Expected = "SELECT * FROM WorkItems WHERE (([Assigned To] = 'danj'))"; + Actual = Query.Where(item => item.AssignedTo == "danj").ToString(); + } + + [TestMethod] + public void the_value_is_written_to_WIQL() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class Given_a_query_with_a_where_clause_on_a_identity_via_indexer : QueryBuilderTests + { + public override void When() + { + base.When(); + Expected = "SELECT * FROM WorkItems WHERE (([Assigned To] = 'danj'))"; + Actual = Query.Where(item => item["Assigned To"].ToString() == "danj").ToString(); + } + + [TestMethod] + public void the_value_is_written_to_WIQL() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class Given_a_query_with_a_where_clause_on_a_identity_via_fields_indexer : QueryBuilderTests + { + public override void When() + { + base.When(); + Expected = "SELECT * FROM WorkItems WHERE (([Assigned To] = 'danj'))"; + Actual = Query.Where(item => item.Fields["Assigned To"].ToString() == "danj").ToString(); + } + + [TestMethod] + public void the_value_is_written_to_WIQL() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + public class Given_a_query_with_a_projection : QueryBuilderTests + { + /// + public override void When() + { + Expected = "SELECT Id FROM WorkItems WHERE (([Id] = 1))"; + Actual = Query.Where(item => item.Id == 1).Select(s => s.Id).ToString(); + } + + [TestMethod] + [ExpectedException(typeof(EqualException))] + public void the_column_written_to_WIQL_in_SELECT_is_the_projected_property() + { + Actual.ShouldEqual(Expected); + } + } } From 63f49d40aefddd81d6e4b81f1a8b99c98671e30b Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 19:15:35 -0700 Subject: [PATCH 134/251] Add review notes --- src/Qwiq.Linq/SimpleFieldMapper.cs | 6 ++++++ src/Qwiq.Linq/WiqlTranslator.cs | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Qwiq.Linq/SimpleFieldMapper.cs b/src/Qwiq.Linq/SimpleFieldMapper.cs index 2312c324..5a99ae7a 100644 --- a/src/Qwiq.Linq/SimpleFieldMapper.cs +++ b/src/Qwiq.Linq/SimpleFieldMapper.cs @@ -4,6 +4,11 @@ namespace Microsoft.Qwiq.Linq { + // REVIEW: A new type should be created that retrieves the field collection from the WIT + + + // TODO: Create a new field mapper that translates known IWorkItem properties to their ReferenceName values + public class SimpleFieldMapper : IFieldMapper { private static readonly Dictionary Mappings = new Dictionary @@ -18,6 +23,7 @@ public class SimpleFieldMapper : IFieldMapper {"RevisedDate", "Revised Date"} }; + // REVIEW: Replace with more constrained set of fields private static readonly IEnumerable FieldNames = new[] {"*"}; public IEnumerable GetWorkItemType(Type type) diff --git a/src/Qwiq.Linq/WiqlTranslator.cs b/src/Qwiq.Linq/WiqlTranslator.cs index 0bb715bf..dfaec170 100644 --- a/src/Qwiq.Linq/WiqlTranslator.cs +++ b/src/Qwiq.Linq/WiqlTranslator.cs @@ -28,7 +28,7 @@ public WiqlTranslator() public WiqlTranslator(IFieldMapper fieldMapper) { - FieldMapper = fieldMapper; + FieldMapper = fieldMapper ?? throw new ArgumentNullException(nameof(fieldMapper)); } protected virtual Translator GetTranslator() @@ -44,9 +44,19 @@ public TranslatedQuery Translate(Expression expression) var query = translator.Query; query.UnderlyingQueryType = query.UnderlyingQueryType ?? TypeSystem.GetElementType(expression.Type); - query.Select = new SelectFragment(FieldMapper.GetFieldNames(query.UnderlyingQueryType).ToList()); - var workItemTypeRestriction = FieldMapper.GetWorkItemType(query.UnderlyingQueryType).ToList(); + if (!query.Projections.Any()) + { + query.Select = new SelectFragment(FieldMapper.GetFieldNames(query.UnderlyingQueryType).ToList()); + } + else + { + // TODO: Enumerate the projections and build the list of fields + query.Select = new SelectFragment(FieldMapper.GetFieldNames(query.UnderlyingQueryType).ToList()); + } + + + var workItemTypeRestriction = FieldMapper.GetWorkItemType(query.UnderlyingQueryType).ToList(); if (workItemTypeRestriction.Any()) { query.WhereClauses.Enqueue(new TypeRestrictionFragment(workItemTypeRestriction)); From e72a1d8e2aa8a0711ec3314bcf4b38d8417bb3dc Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 21 Apr 2017 19:16:12 -0700 Subject: [PATCH 135/251] DRY out properties on Identity.Test project --- .../Qwiq.Identity.Tests.csproj | 41 +++---------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index 3ac78a93..4d744563 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -13,12 +13,16 @@ v4.6 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest true + prompt + 4 + ..\..\build\rulesets\noship.ruleset + true @@ -28,20 +32,13 @@ false bin\Debug\ DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true + false pdbonly true bin\Release\ TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true @@ -55,14 +52,6 @@ - - - - - - - - @@ -96,24 +85,6 @@ - - - - - False - - - False - - - False - - - False - - - - From 4a902be8e66ef004c0a5c97b1f3d2cc020e07b99 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 24 Apr 2017 19:52:56 -0700 Subject: [PATCH 136/251] Relocate IIdentityManagementService Move the IIdentityManagementService to Qwiq.Identity. Move related items for mapping and LINQ to own packages so developers don't need to bring in the entire stack to use a single feature. --- Qwiq.sln | 20 +- Qwiq.sln.DotSettings | 2 +- src/AssemblyInfo.Common.cs | 24 ++ src/Qwiq.Core.Rest/Extensions.cs | 36 +++ src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs | 21 +- src/Qwiq.Core.Rest/Query.cs | 6 +- src/Qwiq.Core.Rest/QueryFactory.cs | 7 +- src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj | 8 +- src/Qwiq.Core.Rest/TeamFoundationIdentity.cs | 16 +- src/Qwiq.Core.Rest/TfsConnectionFactory.cs | 6 +- src/Qwiq.Core.Rest/VssConnectionAdapter.cs | 2 - src/Qwiq.Core.Rest/WorkItemStore.cs | 10 +- src/Qwiq.Core.Rest/packages.config | 2 +- src/Qwiq.Core.Soap/Extensions.cs | 21 ++ .../IdentityManagementService.cs | 67 ------ src/Qwiq.Core.Soap/NodeSelectExtensions.cs | 27 --- src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs | 22 +- src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj | 10 +- src/Qwiq.Core.Soap/TfsConnectionFactory.cs | 5 +- .../TfsTeamProjectCollection.cs | 54 ++--- src/Qwiq.Core.Soap/WorkItemStore.cs | 6 +- src/Qwiq.Core.Soap/packages.config | 2 +- src/Qwiq.Core/IIdentityDescriptor.cs | 3 + src/Qwiq.Core/IIdentityManagementService.cs | 14 -- .../ITeamFoundationIdentity.Extensions.cs | 26 +- src/Qwiq.Core/ITfsTeamProjectCollection.cs | 2 - src/Qwiq.Core/IWorkItemStore.cs | 20 +- src/Qwiq.Core/IdentityFieldValue.cs | 18 +- src/Qwiq.Core/Properties/AssemblyInfo.cs | 17 -- src/Qwiq.Core/Qwiq.Core.csproj | 8 +- src/Qwiq.Core/packages.config | 2 +- src/Qwiq.Identity.Soap/Extensions.cs | 30 +++ .../IdentityManagementService.cs | 77 ++++++ .../Properties/AssemblyInfo.cs | 16 ++ .../Qwiq.Identity.Soap.csproj | 226 ++++++++++++++++++ src/Qwiq.Identity.Soap/app.config | 45 ++++ src/Qwiq.Identity.Soap/packages.config | 17 ++ .../Attributes/IdentityFieldAttribute.cs | 9 - .../IIdentityManagementService.cs | 28 +++ src/Qwiq.Identity/IIdentityValueConverter.cs | 15 ++ .../IdentityAliasValueConverter.cs | 64 +++++ .../Linq/Visitors/IIdentityMapper.cs | 7 - .../Linq/Visitors/IdentityMapper.cs | 40 ---- .../Linq/Visitors/IdentityVisitor.cs | 70 ------ src/Qwiq.Identity/Mapper/WorkItemField.cs | 8 - src/Qwiq.Identity/Properties/AssemblyInfo.cs | 15 +- src/Qwiq.Identity/Qwiq.Identity.csproj | 25 +- src/Qwiq.Identity/packages.config | 3 +- .../Properties/AssemblyInfo.cs | 11 + .../Qwiq.Linq.Identity.csproj | 84 +++++++ .../Qwiq.Linq.Identity.nuspec | 16 ++ .../Visitors/IdentityMappingVisitor.cs | 95 ++++++++ src/Qwiq.Linq.Identity/app.config | 15 ++ src/Qwiq.Linq.Identity/packages.config | 5 + src/Qwiq.Linq/Properties/AssemblyInfo.cs | 13 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 9 +- ...sitor.cs => IdentityComboStringVisitor.cs} | 41 +++- .../Visitors/LocalCollectionExpander.cs | 1 + src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs | 2 +- src/Qwiq.Linq/packages.config | 2 +- .../Attributes/IdentityFieldAttribute.cs | 13 + ...ulkIdentityAwareAttributeMapperStrategy.cs | 30 ++- .../Properties/AssemblyInfo.cs | 15 ++ .../Qwiq.Mapper.Identity.csproj | 95 ++++++++ .../Qwiq.Mapper.Identity.nuspec | 16 ++ src/Qwiq.Mapper.Identity/WorkItemField.cs | 11 + .../WorkItemWithFields.cs | 4 +- src/Qwiq.Mapper.Identity/app.config | 15 ++ src/Qwiq.Mapper.Identity/packages.config | 6 + src/Qwiq.Mapper/Properties/AssemblyInfo.cs | 11 - src/Qwiq.Mapper/Qwiq.Mapper.csproj | 7 +- src/Qwiq.Mapper/packages.config | 2 +- .../Qwiq.Benchmark/Properties/AssemblyInfo.cs | 16 +- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 8 +- test/Qwiq.Benchmark/packages.config | 2 +- .../Properties/AssemblyInfo.cs | 13 +- test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj | 11 +- test/Qwiq.Core.Tests/packages.config | 2 +- .../Benchmark.cs | 1 - .../Properties/AssemblyInfo.cs | 27 +-- .../Qwiq.Identity.Benchmark.Tests.csproj | 14 +- .../packages.config | 3 +- ...entityAwareAttributeMapperStrategyTests.cs | 1 - .../IdentityManagementServiceProxyTests.cs | 8 +- .../IdentityMapperTests.cs | 36 +-- .../Mocks/IdentityMockType.cs | 1 - .../Mocks/MockIdentityDescriptor.cs | 0 .../Mocks/MockIdentityManagementService2.cs | 0 .../Properties/AssemblyInfo.cs | 14 +- .../Qwiq.Identity.Tests.csproj | 172 ++++++++++++- test/Qwiq.Identity.Tests/app.config | 30 +++ test/Qwiq.Identity.Tests/packages.config | 14 +- .../GlobalSuppressions.cs | Bin 5696 -> 8596 bytes ...dentityManagementServiceExtensionsTests.cs | 67 ++++++ .../IdentityMapperTests.cs | 110 +++++++++ .../IntegrationSettings.cs | 5 +- test/Qwiq.Integration.Tests/LinqTests.cs | 53 ++++ .../Properties/AssemblyInfo.cs | 8 - .../Qwiq.Integration.Tests.csproj | 24 ++ test/Qwiq.Integration.Tests/packages.config | 1 + .../Properties/AssemblyInfo.cs | 13 +- test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj | 7 +- test/Qwiq.Linq.Tests/packages.config | 2 +- .../Properties/AssemblyInfo.cs | 30 +-- .../Qwiq.Mapper.Benchmark.Tests.csproj | 9 +- .../packages.config | 3 +- .../Mocks/InstrumentedMockWorkItemStore.cs | 6 +- .../Properties/AssemblyInfo.cs | 16 +- .../Qwiq.Mapper.Tests.csproj | 7 +- test/Qwiq.Mapper.Tests/packages.config | 2 +- test/Qwiq.Mocks/Identities.cs | 47 ++++ test/Qwiq.Mocks/MockIdentityDescriptor.cs | 14 +- .../MockIdentityManagementService.cs | 70 ++---- test/Qwiq.Mocks/MockTeamFoundationIdentity.cs | 2 +- .../MockTfsTeamProjectCollection.cs | 1 + test/Qwiq.Mocks/MockWorkItemStore.cs | 12 +- test/Qwiq.Mocks/Properties/AssemblyInfo.cs | 14 -- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 12 +- test/Qwiq.Mocks/packages.config | 2 +- .../Properties/AssemblyInfo.cs | 11 - .../Qwiq.Tests.Common.csproj | 7 +- test/Qwiq.Tests.Common/packages.config | 2 +- 122 files changed, 1858 insertions(+), 770 deletions(-) create mode 100644 src/AssemblyInfo.Common.cs create mode 100644 src/Qwiq.Core.Rest/Extensions.cs create mode 100644 src/Qwiq.Core.Soap/Extensions.cs delete mode 100644 src/Qwiq.Core.Soap/IdentityManagementService.cs delete mode 100644 src/Qwiq.Core.Soap/NodeSelectExtensions.cs delete mode 100644 src/Qwiq.Core/IIdentityManagementService.cs create mode 100644 src/Qwiq.Identity.Soap/Extensions.cs create mode 100644 src/Qwiq.Identity.Soap/IdentityManagementService.cs create mode 100644 src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs create mode 100644 src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj create mode 100644 src/Qwiq.Identity.Soap/app.config create mode 100644 src/Qwiq.Identity.Soap/packages.config delete mode 100644 src/Qwiq.Identity/Attributes/IdentityFieldAttribute.cs create mode 100644 src/Qwiq.Identity/IIdentityManagementService.cs create mode 100644 src/Qwiq.Identity/IIdentityValueConverter.cs create mode 100644 src/Qwiq.Identity/IdentityAliasValueConverter.cs delete mode 100644 src/Qwiq.Identity/Linq/Visitors/IIdentityMapper.cs delete mode 100644 src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs delete mode 100644 src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs delete mode 100644 src/Qwiq.Identity/Mapper/WorkItemField.cs create mode 100644 src/Qwiq.Linq.Identity/Properties/AssemblyInfo.cs create mode 100644 src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj create mode 100644 src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.nuspec create mode 100644 src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs create mode 100644 src/Qwiq.Linq.Identity/app.config create mode 100644 src/Qwiq.Linq.Identity/packages.config rename src/Qwiq.Linq/Visitors/{IdentityVisitor.cs => IdentityComboStringVisitor.cs} (54%) create mode 100644 src/Qwiq.Mapper.Identity/Attributes/IdentityFieldAttribute.cs rename src/{Qwiq.Identity/Mapper => Qwiq.Mapper.Identity}/BulkIdentityAwareAttributeMapperStrategy.cs (80%) create mode 100644 src/Qwiq.Mapper.Identity/Properties/AssemblyInfo.cs create mode 100644 src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj create mode 100644 src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.nuspec create mode 100644 src/Qwiq.Mapper.Identity/WorkItemField.cs rename src/{Qwiq.Identity/Mapper => Qwiq.Mapper.Identity}/WorkItemWithFields.cs (62%) create mode 100644 src/Qwiq.Mapper.Identity/app.config create mode 100644 src/Qwiq.Mapper.Identity/packages.config rename test/{Qwiq.Core.Tests => Qwiq.Identity.Tests}/IdentityManagementServiceProxyTests.cs (91%) rename test/{Qwiq.Core.Tests => Qwiq.Identity.Tests}/Mocks/MockIdentityDescriptor.cs (100%) rename test/{Qwiq.Core.Tests => Qwiq.Identity.Tests}/Mocks/MockIdentityManagementService2.cs (100%) create mode 100644 test/Qwiq.Integration.Tests/IdentityManagementServiceExtensionsTests.cs create mode 100644 test/Qwiq.Integration.Tests/IdentityMapperTests.cs create mode 100644 test/Qwiq.Integration.Tests/LinqTests.cs create mode 100644 test/Qwiq.Mocks/Identities.cs diff --git a/Qwiq.sln b/Qwiq.sln index 08f8b9bb..c5ba974e 100644 --- a/Qwiq.sln +++ b/Qwiq.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.3 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qwiq.Core.Tests", "test\Qwiq.Core.Tests\Qwiq.Core.Tests.csproj", "{57407CCA-8444-4713-95E9-CFC1168D846B}" EndProject @@ -35,6 +35,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qwiq.Core.Rest", "src\Qwiq. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qwiq.Integration.Tests", "test\Qwiq.Integration.Tests\Qwiq.Integration.Tests.csproj", "{E4130432-C890-41E0-8407-C4142CAF59D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qwiq.Identity.Soap", "src\Qwiq.Identity.Soap\Qwiq.Identity.Soap.csproj", "{2B588D8C-5E01-4B48-96A7-B961FC54A4AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qwiq.Mapper.Identity", "src\Qwiq.Mapper.Identity\Qwiq.Mapper.Identity.csproj", "{BE25CF2D-FA53-4455-85B1-4EEC1D979FB1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qwiq.Linq.Identity", "src\Qwiq.Linq.Identity\Qwiq.Linq.Identity.csproj", "{0451D5EA-0206-48A6-A759-DC6572C4CD39}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +111,18 @@ Global {E4130432-C890-41E0-8407-C4142CAF59D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {E4130432-C890-41E0-8407-C4142CAF59D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {E4130432-C890-41E0-8407-C4142CAF59D8}.Release|Any CPU.Build.0 = Release|Any CPU + {2B588D8C-5E01-4B48-96A7-B961FC54A4AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B588D8C-5E01-4B48-96A7-B961FC54A4AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B588D8C-5E01-4B48-96A7-B961FC54A4AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B588D8C-5E01-4B48-96A7-B961FC54A4AC}.Release|Any CPU.Build.0 = Release|Any CPU + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1}.Release|Any CPU.Build.0 = Release|Any CPU + {0451D5EA-0206-48A6-A759-DC6572C4CD39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0451D5EA-0206-48A6-A759-DC6572C4CD39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0451D5EA-0206-48A6-A759-DC6572C4CD39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0451D5EA-0206-48A6-A759-DC6572C4CD39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Qwiq.sln.DotSettings b/Qwiq.sln.DotSettings index b4cfa9a3..fd20d9a4 100644 --- a/Qwiq.sln.DotSettings +++ b/Qwiq.sln.DotSettings @@ -1,2 +1,2 @@  - <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Integration.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Integration.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Visitors.Utility" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Visitors.LocalCollectionExpander" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file diff --git a/src/AssemblyInfo.Common.cs b/src/AssemblyInfo.Common.cs new file mode 100644 index 00000000..581acd52 --- /dev/null +++ b/src/AssemblyInfo.Common.cs @@ -0,0 +1,24 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Microsoft.Qwiq")] +[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] + +[assembly: ComVisible(false)] + +[assembly: CLSCompliant(true)] + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + +[assembly: NeutralResourcesLanguage("en-US")] + +#if DEBUG +[assembly:AssemblyConfiguration("Debug")] +#else +[assembly:AssemblyConfiguration("Release")] +#endif \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Extensions.cs b/src/Qwiq.Core.Rest/Extensions.cs new file mode 100644 index 00000000..5e6dbf17 --- /dev/null +++ b/src/Qwiq.Core.Rest/Extensions.cs @@ -0,0 +1,36 @@ +using Microsoft.Qwiq.Exceptions; +using Microsoft.VisualStudio.Services.WebApi; + +namespace Microsoft.Qwiq.Rest +{ + internal static class Extensions + { + internal static IWorkItem AsProxy(this WorkItem item) + { + return item == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(item); + } + + internal static IQuery AsProxy(this Query query) + { + return query == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(query); + } + + internal static IIdentityDescriptor AsProxy(this VisualStudio.Services.Identity.IdentityDescriptor value) + { + return value == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptor(value)); + } + + internal static IInternalTeamProjectCollection AsProxy(this VssConnection tfsNative) + { + return tfsNative == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(new VssConnectionAdapter(tfsNative)); + } + } +} diff --git a/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs b/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs index 67a08771..6ed80ad4 100644 --- a/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core.Rest/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System; -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,28 +6,12 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Core.Rest")] -[assembly: AssemblyDescription("Provides Quick Workitem Queries to TFS and Visual Studio Online")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyDescription("Provides Quick Work Item Queries to Team Foundation Server and Visual Studio Online via REST APIs")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("852f1b8e-336d-4a55-80b8-eed7ddb68619")] -[assembly: CLSCompliant(true)] - [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Tests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Mocks")] -[assembly: InternalsVisibleTo("Qwiq.Mocks")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Soap")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Integration.Tests")] diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 6decafff..8ee2b8dd 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -4,7 +4,6 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; -using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Rest @@ -113,11 +112,10 @@ IWorkItemType WorkItemTypeFactory() return _workItemStore.Projects[proj].WorkItemTypes[witName]; } - yield return ExceptionHandlingDynamicProxyFactory.Create( - new WorkItem( + yield return new WorkItem( workItem, new Lazy(WorkItemTypeFactory), - s => _workItemStore.WorkItemLinkTypes[s])); + s => _workItemStore.WorkItemLinkTypes[s]).AsProxy(); } } diff --git a/src/Qwiq.Core.Rest/QueryFactory.cs b/src/Qwiq.Core.Rest/QueryFactory.cs index ce119385..b5c29081 100644 --- a/src/Qwiq.Core.Rest/QueryFactory.cs +++ b/src/Qwiq.Core.Rest/QueryFactory.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; -using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Rest @@ -18,14 +17,12 @@ private QueryFactory(WorkItemStore store) public IQuery Create(string wiql, bool dayPrecision) { - return ExceptionHandlingDynamicProxyFactory.Create( - new Query(new Wiql { Query = wiql }, dayPrecision, _store)); + return new Query(new Wiql { Query = wiql }, dayPrecision, _store).AsProxy(); } public IQuery Create(IEnumerable ids, string wiql) { - return ExceptionHandlingDynamicProxyFactory.Create( - new Query(ids, new Wiql { Query = wiql }, _store)); + return new Query(ids, new Wiql { Query = wiql }, _store).AsProxy(); } public IQuery Create(IEnumerable ids, DateTime? asOf = null) diff --git a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj index a08d8593..5a21f5d3 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Core.Rest.csproj @@ -1,6 +1,6 @@  - + Debug @@ -100,6 +100,10 @@ + + Properties\AssemblyInfo.Common.cs + + @@ -139,6 +143,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs index 6966ee7b..06e628ad 100644 --- a/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Qwiq.Exceptions; using Microsoft.VisualStudio.Services.Identity; namespace Microsoft.Qwiq.Rest @@ -25,18 +24,9 @@ internal TeamFoundationIdentity(Identity identity) IsContainer = identity.IsContainer; - _descriptor = new Lazy( - () => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptor(identity.Descriptor))); - _memberOf = new Lazy>( - () => identity.MemberOf.Select( - item => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptor(item)))); - - _members = new Lazy>( - () => identity.Members.Select( - item => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityDescriptor(item)))); + _descriptor = new Lazy(() => identity.Descriptor.AsProxy()); + _memberOf = new Lazy>(() => identity.MemberOf.Select(item => item.AsProxy())); + _members = new Lazy>(() => identity.Members.Select(item => item.AsProxy())); } public override IIdentityDescriptor Descriptor => _descriptor.Value; diff --git a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs index b420b841..a8737f20 100644 --- a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs +++ b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.Build.WebApi; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; @@ -27,10 +26,7 @@ public override ITeamProjectCollection Create(AuthenticationOptions options) try { var tfsNative = ConnectToTfsCollection(options.Uri, credential); - var tfsProxy = - ExceptionHandlingDynamicProxyFactory - .Create(new VssConnectionAdapter(tfsNative)); - + var tfsProxy = tfsNative.AsProxy(); options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfsProxy)); return tfsProxy; diff --git a/src/Qwiq.Core.Rest/VssConnectionAdapter.cs b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs index 29a01fdf..d0078fc9 100644 --- a/src/Qwiq.Core.Rest/VssConnectionAdapter.cs +++ b/src/Qwiq.Core.Rest/VssConnectionAdapter.cs @@ -27,8 +27,6 @@ internal VssConnectionAdapter(VssConnection connection) public bool HasAuthenticated { get; } - public IIdentityManagementService IdentityManagementService { get; } - public TimeZone TimeZone { get; } public Uri Uri { get; } diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index b6eb083b..cd1012c6 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -59,7 +59,7 @@ WorkItemLinkTypeCollection ValueFactory() return GetLinks(NativeWorkItemStore.Value); } - + _linkTypes = new Lazy(ValueFactory); _projects = new Lazy( @@ -88,13 +88,9 @@ WorkItemLinkTypeCollection ValueFactory() public ITeamProjectCollection TeamProjectCollection => _tfs.Value; - public TimeZone TimeZone => TeamProjectCollection?.TimeZone ?? TimeZone.CurrentTimeZone; - - public string UserAccountName => TeamProjectCollection.AuthorizedIdentity.GetUserAlias(); + public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection.AuthorizedIdentity; - public string UserDisplayName => TeamProjectCollection.AuthorizedIdentity.DisplayName; - - public string UserSid => TeamProjectCollection.AuthorizedIdentity.Descriptor.Identifier; + public TimeZone TimeZone => TeamProjectCollection?.TimeZone ?? TimeZone.CurrentTimeZone; public IWorkItemLinkTypeCollection WorkItemLinkTypes => _linkTypes.Value; diff --git a/src/Qwiq.Core.Rest/packages.config b/src/Qwiq.Core.Rest/packages.config index 694c08ef..12f596c1 100644 --- a/src/Qwiq.Core.Rest/packages.config +++ b/src/Qwiq.Core.Rest/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Qwiq.Core.Soap/Extensions.cs b/src/Qwiq.Core.Soap/Extensions.cs new file mode 100644 index 00000000..436be1d4 --- /dev/null +++ b/src/Qwiq.Core.Soap/Extensions.cs @@ -0,0 +1,21 @@ +using Microsoft.Qwiq.Exceptions; + +namespace Microsoft.Qwiq.Soap +{ + internal static class Extensions + { + internal static ITeamFoundationIdentity AsProxy(this TeamFoundation.Framework.Client.TeamFoundationIdentity identity) + { + return identity == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(new TeamFoundationIdentity(identity)); + } + + internal static IInternalTeamProjectCollection AsProxy(this TeamFoundation.Client.TfsTeamProjectCollection tfsNative) + { + return tfsNative == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(new TfsTeamProjectCollection(tfsNative)); + } + } +} diff --git a/src/Qwiq.Core.Soap/IdentityManagementService.cs b/src/Qwiq.Core.Soap/IdentityManagementService.cs deleted file mode 100644 index a4d296e4..00000000 --- a/src/Qwiq.Core.Soap/IdentityManagementService.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Microsoft.Qwiq.Exceptions; - -using Tfs = Microsoft.TeamFoundation.Framework; - -namespace Microsoft.Qwiq.Soap -{ - internal class IdentityManagementService : IIdentityManagementService - { - private readonly Tfs.Client.IIdentityManagementService2 _identityManagementService2; - - internal IdentityManagementService(Tfs.Client.IIdentityManagementService2 identityManagementService2) - { - _identityManagementService2 = identityManagementService2 ?? throw new ArgumentNullException(nameof(identityManagementService2)); - } - - public IEnumerable ReadIdentities(ICollection descriptors) - { - var rawDescriptors = descriptors.Select(descriptor => - new Tfs.Client.IdentityDescriptor(descriptor.IdentityType, descriptor.Identifier)).ToArray(); - - var identities = _identityManagementService2.ReadIdentities(rawDescriptors, Tfs.Common.MembershipQuery.None, - Tfs.Common.ReadIdentityOptions.IncludeReadFromSource); - - return identities.Select(identity => identity == null ? null : ExceptionHandlingDynamicProxyFactory.Create(new TeamFoundationIdentity(identity))); - } - - public IEnumerable>> ReadIdentities(IdentitySearchFactor searchFactor, ICollection searchFactorValues) - { - var searchFactorArray = searchFactorValues.ToArray(); - var factor = (Tfs.Common.IdentitySearchFactor) searchFactor; - var identities = _identityManagementService2.ReadIdentities(factor, searchFactorArray, - Tfs.Common.MembershipQuery.None, Tfs.Common.ReadIdentityOptions.IncludeReadFromSource); - - if (searchFactorArray.Length != identities.Length) - { - throw new IndexOutOfRangeException("A call to IIdentityManagementService2.ReadIdentities resulted in a return set where there was not a one to one mapping between search terms and search results. This is unexpected behavior and execution cannot continue. Please check if the underlying service implementation has changed and update the consuming code as appropriate."); - } - - for (var i = 0; i < searchFactorArray.Length; i++) - { - var proxiedIdentities = identities[i].Select(TryCreateProxy); - yield return new KeyValuePair>(searchFactorArray[i], proxiedIdentities); - } - } - - public IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier) - { - return ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptor(new Tfs.Client.IdentityDescriptor(identityType, identifier))); - } - - private ITeamFoundationIdentity TryCreateProxy(Tfs.Client.TeamFoundationIdentity identity) - { - if (identity == null) - { - return null; - } - - return - ExceptionHandlingDynamicProxyFactory.Create( - new TeamFoundationIdentity(identity)); - } - } -} diff --git a/src/Qwiq.Core.Soap/NodeSelectExtensions.cs b/src/Qwiq.Core.Soap/NodeSelectExtensions.cs deleted file mode 100644 index 53d2d852..00000000 --- a/src/Qwiq.Core.Soap/NodeSelectExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Globalization; - -// ReSharper disable CheckNamespace -namespace Microsoft.TeamFoundation.WorkItemTracking.Client.Wiql -// ReSharper restore CheckNamespace -{ - public static class NodeSelectExtensions - { - /// - /// Gets the as in UTC. - /// - /// - /// If value is not already in UTC is returned. - public static DateTime? GetAsOfUtc(this NodeSelect nodeSelect) - { - if (nodeSelect.AsOf == null || - !DateTime.TryParse(((NodeItem)nodeSelect.AsOf).Value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime result)) - return null; - if (result.Kind != DateTimeKind.Utc) - { - throw new ArgumentOutOfRangeException(nameof(nodeSelect.AsOf), "Specified date must be UTC"); - } - return result; - } - } -} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs b/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs index ffbaab0e..3e763736 100644 --- a/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core.Soap/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System; -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,28 +6,13 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Core.Soap")] -[assembly: AssemblyDescription("Provides Quick Workitem Queries to TFS and Visual Studio Online")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyDescription("Provides Quick Work Item Queries to Team Foundation Server and Visual Studio Online via SOAP APIs")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("5516d63e-16f0-4f5f-bd9c-76d21b6d8aa3")] -[assembly: CLSCompliant(true)] - [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Tests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Mocks")] -[assembly: InternalsVisibleTo("Qwiq.Mocks")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Rest")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Integration.Tests")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Soap")] diff --git a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj index 0d71c270..71268592 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Core.Soap.csproj @@ -1,6 +1,6 @@  - + Debug @@ -194,8 +194,12 @@ + + Properties\AssemblyInfo.Common.cs + + @@ -204,7 +208,6 @@ - @@ -215,7 +218,6 @@ - @@ -262,8 +264,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/TfsConnectionFactory.cs b/src/Qwiq.Core.Soap/TfsConnectionFactory.cs index 653cfbab..7fee74c7 100644 --- a/src/Qwiq.Core.Soap/TfsConnectionFactory.cs +++ b/src/Qwiq.Core.Soap/TfsConnectionFactory.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.Build.Client; using Microsoft.VisualStudio.Services.Common; @@ -26,9 +25,7 @@ public override ITeamProjectCollection Create(AuthenticationOptions options) try { var tfsNative = ConnectToTfsCollection(options.Uri, credential); - var tfsProxy = - ExceptionHandlingDynamicProxyFactory.Create( - new TfsTeamProjectCollection(tfsNative)); + var tfsProxy = tfsNative.AsProxy(); options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfsProxy)); diff --git a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs index 8d5c61e9..f1898d8f 100644 --- a/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs +++ b/src/Qwiq.Core.Soap/TfsTeamProjectCollection.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.Framework.Client; using Microsoft.TeamFoundation.Server; using Microsoft.VisualStudio.Services.Common; @@ -14,10 +13,6 @@ internal class TfsTeamProjectCollection : IInternalTeamProjectCollection { private readonly Lazy _css; - private readonly Lazy _ims; - - private readonly TeamFoundation.Client.TfsTeamProjectCollection _tfs; - /// /// Initializes a new instance of the class. /// @@ -25,18 +20,21 @@ internal class TfsTeamProjectCollection : IInternalTeamProjectCollection /// tfs internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection teamProjectCollection) { - _tfs = teamProjectCollection ?? throw new ArgumentNullException(nameof(teamProjectCollection)); + Native = teamProjectCollection ?? throw new ArgumentNullException(nameof(teamProjectCollection)); - AuthorizedCredentials = _tfs.ClientCredentials; - AuthorizedIdentity = new TeamFoundationIdentity(_tfs.AuthorizedIdentity); - Uri = _tfs.Uri; + AuthorizedCredentials = Native.ClientCredentials; + AuthorizedIdentity = new TeamFoundationIdentity(Native.AuthorizedIdentity); + Uri = Native.Uri; _css = new Lazy( - () => ExceptionHandlingDynamicProxyFactory.Create( - new CommonStructureService(_tfs.GetService()))); - _ims = new Lazy( - () => ExceptionHandlingDynamicProxyFactory.Create( - new IdentityManagementService(_tfs.GetService()))); + () => ExceptionHandlingDynamicProxyFactory.Create( + new + CommonStructureService( + Native + .GetService + < + ICommonStructureService4 + >()))); } /// @@ -61,19 +59,13 @@ internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection /// Returns true if this object has successfully authenticated. /// /// true if this instance has authenticated; otherwise, false. - public bool HasAuthenticated => _tfs.HasAuthenticated; - - /// - /// Gets the identity management service. - /// - /// The identity management service. - public IIdentityManagementService IdentityManagementService => _ims.Value; + public bool HasAuthenticated => Native.HasAuthenticated; /// /// This is used to convert dates and times to UTC. /// /// The time zone. - public TimeZone TimeZone => _tfs.TimeZone; + public TimeZone TimeZone => Native.TimeZone; /// /// The base url for this connection @@ -81,6 +73,8 @@ internal TfsTeamProjectCollection(TeamFoundation.Client.TfsTeamProjectCollection /// The URI. public Uri Uri { get; } + internal TeamFoundation.Client.TfsTeamProjectCollection Native { get; } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -97,7 +91,7 @@ public void Dispose() /// T. public T GetClient() { - return _tfs.GetClient(); + return Native.GetClient(); } /// @@ -107,7 +101,12 @@ public T GetClient() /// T. public T GetService() { - return _tfs.GetService(); + return Native.GetService(); + } + + public override string ToString() + { + return Native.Name; } /// @@ -119,12 +118,7 @@ public T GetService() /// protected virtual void Dispose(bool disposing) { - if (disposing) _tfs?.Dispose(); - } - - public override string ToString() - { - return _tfs.Name; + if (disposing) Native?.Dispose(); } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index 4c9cc7ad..64166bcd 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -86,11 +86,7 @@ internal WorkItemStore( public TimeZone TimeZone => _workItemStore.Value.TimeZone; - public string UserDisplayName => _workItemStore.Value.UserDisplayName; - - public string UserAccountName => TeamProjectCollection.AuthorizedIdentity.GetUserAlias(); - - public string UserSid => _workItemStore.Value.UserSid; + public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection?.AuthorizedIdentity; public IWorkItemLinkTypeCollection WorkItemLinkTypes => _workItemLinkTypes.Value; diff --git a/src/Qwiq.Core.Soap/packages.config b/src/Qwiq.Core.Soap/packages.config index 49b2771b..9605a2e3 100644 --- a/src/Qwiq.Core.Soap/packages.config +++ b/src/Qwiq.Core.Soap/packages.config @@ -4,7 +4,7 @@ - + diff --git a/src/Qwiq.Core/IIdentityDescriptor.cs b/src/Qwiq.Core/IIdentityDescriptor.cs index 3ff69718..12774a0a 100644 --- a/src/Qwiq.Core/IIdentityDescriptor.cs +++ b/src/Qwiq.Core/IIdentityDescriptor.cs @@ -1,5 +1,8 @@ namespace Microsoft.Qwiq { + /// + /// Represents an identity descriptor + . + /// public interface IIdentityDescriptor { string Identifier { get; } diff --git a/src/Qwiq.Core/IIdentityManagementService.cs b/src/Qwiq.Core/IIdentityManagementService.cs deleted file mode 100644 index b8b2a536..00000000 --- a/src/Qwiq.Core/IIdentityManagementService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.Qwiq -{ - public interface IIdentityManagementService - { - IEnumerable ReadIdentities(ICollection descriptors); - - IEnumerable>> ReadIdentities(IdentitySearchFactor searchFactor, ICollection searchFactorValues); - - IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier); - } -} - diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs index 6ca9aeda..6f752ae5 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs @@ -1,21 +1,33 @@ -using System; - using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq { // ReSharper disable InconsistentNaming - public static class ITeamFoundationIdentityExtensions - // ReSharper restore InconsistentNaming + public static partial class Extensions + // ReSharper restore InconsistentNaming { + public static string GetUserAlias(this ITeamFoundationIdentity identity) { - if (identity == null) throw new ArgumentNullException(nameof(identity)); + if (identity == null) return null; - var alias = identity.GetAttribute(IdentityAttributeTags.AccountName, null) + return identity.GetAttribute(IdentityAttributeTags.Alias, null) ?? new IdentityFieldValue(identity).Alias; + } + + public static string GetUserAccountName(this ITeamFoundationIdentity identity) + { + if (identity == null) return null; + + return identity.GetAttribute(IdentityAttributeTags.AccountName, null) + ?? new IdentityFieldValue(identity).AccountName; + } + + public static string GetIdentityName(this ITeamFoundationIdentity identity) + { + if (identity == null) return null; - return alias; + return identity.GetAttribute(IdentityAttributeTags.AadUserPrincipalName, null) ?? new IdentityFieldValue(identity).IdentityName; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/ITfsTeamProjectCollection.cs b/src/Qwiq.Core/ITfsTeamProjectCollection.cs index 78067e63..7a941609 100644 --- a/src/Qwiq.Core/ITfsTeamProjectCollection.cs +++ b/src/Qwiq.Core/ITfsTeamProjectCollection.cs @@ -24,8 +24,6 @@ public interface ITeamProjectCollection /// Returns true if this object has successfully authenticated. bool HasAuthenticated { get; } - IIdentityManagementService IdentityManagementService { get; } - /// This is used to convert dates and times to UTC. TimeZone TimeZone { get; } diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index dde3ef0c..054435fb 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -15,6 +15,8 @@ public interface IWorkItemStore : IDisposable /// The authorized credentials. VssCredentials AuthorizedCredentials { get; } + ITeamFoundationIdentity AuthorizedIdentity { get; } + /// /// Indicates the communication type used for the work item store. /// @@ -46,24 +48,6 @@ public interface IWorkItemStore : IDisposable /// The time zone. TimeZone TimeZone { get; } - /// - /// Gets the name of the user account. - /// - /// The name of the user account. - string UserAccountName { get; } - - /// - /// Gets the display name of the user. - /// - /// The display name of the user. - string UserDisplayName { get; } - - /// - /// Gets the user sid. - /// - /// The user sid. - string UserSid { get; } - /// /// Gets the work item link types associated with this instance. /// diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index 115e93a5..607b147f 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; @@ -10,7 +9,6 @@ namespace Microsoft.Qwiq /// /// Class IdentityFieldValue. /// - [DebuggerDisplay("{" + nameof(DisplayName) + "}")] public class IdentityFieldValue { // "Chris Johnson " @@ -252,5 +250,21 @@ private static bool TryGetVsid(string search, out Guid vsid, out string displayN displayName = string.Empty; return false; } + + /// + public override string ToString() + { + if (string.IsNullOrEmpty(IdentityName)) + { + return DisplayName; + } + + return ($"{DisplayName} <{AccountName}>").ToString(CultureInfo.InvariantCulture); + } + + public static explicit operator string(IdentityFieldValue value) + { + return value.IdentityName; + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/Properties/AssemblyInfo.cs b/src/Qwiq.Core/Properties/AssemblyInfo.cs index 84b0c9f7..67c6fbc4 100644 --- a/src/Qwiq.Core/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core/Properties/AssemblyInfo.cs @@ -1,4 +1,3 @@ -using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -8,28 +7,12 @@ // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Core")] [assembly: AssemblyDescription("Provides Quick Work Item Queries to Team Foundation Server and Visual Studio Online")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("1f6293cb-bcca-4038-a696-4358d285b986")] -[assembly: CLSCompliant(true)] - [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Tests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Mocks")] -[assembly: InternalsVisibleTo("Qwiq.Mocks")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Soap")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.Rest")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Integration.Tests")] diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 682754ed..b1cd68ad 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -1,6 +1,6 @@  - + Debug @@ -85,6 +85,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -144,7 +147,6 @@ - @@ -244,6 +246,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index f0c5618a..d1bc9b15 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -4,7 +4,7 @@ - + diff --git a/src/Qwiq.Identity.Soap/Extensions.cs b/src/Qwiq.Identity.Soap/Extensions.cs new file mode 100644 index 00000000..57f12b60 --- /dev/null +++ b/src/Qwiq.Identity.Soap/Extensions.cs @@ -0,0 +1,30 @@ +using Microsoft.Qwiq.Exceptions; +using Microsoft.TeamFoundation.Framework.Client; + +namespace Microsoft.Qwiq.Identity.Soap +{ + public static class Extensions + { + internal static IIdentityDescriptor AsProxy(this TeamFoundation.Framework.Client.IdentityDescriptor descriptor) + { + return ExceptionHandlingDynamicProxyFactory.Create(new Microsoft.Qwiq.Soap.IdentityDescriptor(descriptor)); + } + + internal static IIdentityManagementService AsProxy(this IIdentityManagementService2 ims) + { + return ims == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(new IdentityManagementService(ims)); + } + + public static IIdentityManagementService GetIdentityManagementService(this ITeamProjectCollection tpc) + { + return ((Qwiq.Soap.IInternalTeamProjectCollection)tpc).GetService().AsProxy(); + } + + public static IIdentityManagementService GetIdentityManagementService(this IWorkItemStore wis) + { + return wis.TeamProjectCollection.GetIdentityManagementService(); + } + } +} diff --git a/src/Qwiq.Identity.Soap/IdentityManagementService.cs b/src/Qwiq.Identity.Soap/IdentityManagementService.cs new file mode 100644 index 00000000..9b748943 --- /dev/null +++ b/src/Qwiq.Identity.Soap/IdentityManagementService.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Qwiq.Soap; +using Microsoft.TeamFoundation.Framework.Client; +using Microsoft.TeamFoundation.Framework.Common; + +namespace Microsoft.Qwiq.Identity.Soap +{ + internal class IdentityManagementService : IIdentityManagementService + { + private readonly IIdentityManagementService2 _identityManagementService2; + + internal IdentityManagementService(IIdentityManagementService2 identityManagementService2) + { + _identityManagementService2 = identityManagementService2 ?? throw new ArgumentNullException(nameof(identityManagementService2)); + } + + public IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier) + { + return new TeamFoundation.Framework.Client.IdentityDescriptor(identityType, identifier).AsProxy(); + } + + public IEnumerable ReadIdentities(IEnumerable descriptors) + { + var rawDescriptors = descriptors.Select( + descriptor => new TeamFoundation.Framework.Client.IdentityDescriptor( + descriptor + .IdentityType, + descriptor + .Identifier)) + .ToArray(); + + var identities = + _identityManagementService2.ReadIdentities( + rawDescriptors, + MembershipQuery.None, + ReadIdentityOptions.IncludeReadFromSource); + + return identities.Select(identity => identity?.AsProxy()); + } + + public IEnumerable>> ReadIdentities( + IdentitySearchFactor searchFactor, + IEnumerable searchFactorValues) + { + var searchFactorArray = searchFactorValues.ToArray(); + var factor = (TeamFoundation.Framework.Common.IdentitySearchFactor)searchFactor; + var identities = _identityManagementService2.ReadIdentities( + factor, + searchFactorArray, + MembershipQuery.None, + ReadIdentityOptions.IncludeReadFromSource); + + if (searchFactorArray.Length != identities.Length) + throw new IndexOutOfRangeException( + "A call to IIdentityManagementService2.ReadIdentities resulted in a return set where there was not a one to one mapping between search terms and search results. This is unexpected behavior and execution cannot continue. Please check if the underlying service implementation has changed and update the consuming code as appropriate."); + + for (var i = 0; i < searchFactorArray.Length; i++) + { + var proxiedIdentities = identities[i].Select(id=>id.AsProxy()); + yield return new KeyValuePair>(searchFactorArray[i], proxiedIdentities); + } + } + + public ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue) + { + return _identityManagementService2.ReadIdentity( + (TeamFoundation.Framework.Common.IdentitySearchFactor)searchFactor, + searchFactorValue, + MembershipQuery.None, + ReadIdentityOptions.IncludeReadFromSource) + .AsProxy(); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs b/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b8d48e57 --- /dev/null +++ b/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Qwiq.Identity.Soap")] +[assembly: AssemblyDescription("Provides identity extension methods on top of Microsoft.Qwiq.Core")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2b588d8c-5e01-4b48-96a7-b961fc54a4ac")] + + +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Integration.Tests")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Tests")] diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj new file mode 100644 index 00000000..7988cab2 --- /dev/null +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj @@ -0,0 +1,226 @@ + + + + + + Debug + AnyCPU + {2B588D8C-5E01-4B48-96A7-B961FC54A4AC} + Library + Properties + Microsoft.Qwiq.Identity.Soap + Microsoft.Qwiq.Identity.Soap + v4.6 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + + ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll + + + ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll + True + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll + True + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll + + + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + + + + ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + + + + + + Properties\AssemblyInfo.Common.cs + + + + + + + + + + + + {8AC61B6E-BEC1-482D-A043-C65D2D343B35} + Qwiq.Core + + + {6F5FFC42-0539-4161-B348-A54ADB57C2BD} + Qwiq.Core.Soap + + + {B3654D2D-B4D4-405C-9AEC-FC1859A87E74} + Qwiq.Identity + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Identity.Soap/app.config b/src/Qwiq.Identity.Soap/app.config new file mode 100644 index 00000000..ab4357fc --- /dev/null +++ b/src/Qwiq.Identity.Soap/app.config @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Identity.Soap/packages.config b/src/Qwiq.Identity.Soap/packages.config new file mode 100644 index 00000000..9605a2e3 --- /dev/null +++ b/src/Qwiq.Identity.Soap/packages.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Identity/Attributes/IdentityFieldAttribute.cs b/src/Qwiq.Identity/Attributes/IdentityFieldAttribute.cs deleted file mode 100644 index e64a6df2..00000000 --- a/src/Qwiq.Identity/Attributes/IdentityFieldAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Microsoft.Qwiq.Identity.Attributes -{ - [AttributeUsage(AttributeTargets.Property)] - public class IdentityFieldAttribute : Attribute - { - } -} diff --git a/src/Qwiq.Identity/IIdentityManagementService.cs b/src/Qwiq.Identity/IIdentityManagementService.cs new file mode 100644 index 00000000..23280ee4 --- /dev/null +++ b/src/Qwiq.Identity/IIdentityManagementService.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Microsoft.Qwiq.Identity +{ + public interface IIdentityManagementService + { + IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier); + + /// + /// Read identities for given . + /// + /// A set of s + /// + IEnumerable ReadIdentities(IEnumerable descriptors); + + /// + /// Read identities for given and . + /// + /// Specific search. + /// Actual search strings. + /// An enumerable set of identities corresponding 1 to 1 with . + IEnumerable>> ReadIdentities( + IdentitySearchFactor searchFactor, + IEnumerable searchFactorValues); + + ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue); + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity/IIdentityValueConverter.cs b/src/Qwiq.Identity/IIdentityValueConverter.cs new file mode 100644 index 00000000..bcba6572 --- /dev/null +++ b/src/Qwiq.Identity/IIdentityValueConverter.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Qwiq.Identity +{ + /// + /// Defines a method that converts the value of the implementing reference or value type to another reference or value type. + /// + public interface IIdentityValueConverter + { + /// + /// Converts the specified to an . + /// + /// + /// An instance whose value is equivilent to the value of . + object Map(object value); + } +} diff --git a/src/Qwiq.Identity/IdentityAliasValueConverter.cs b/src/Qwiq.Identity/IdentityAliasValueConverter.cs new file mode 100644 index 00000000..ba532578 --- /dev/null +++ b/src/Qwiq.Identity/IdentityAliasValueConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq.Identity +{ + + /// + /// Converts a representing an alias to a user principal name. + /// + /// + public class IdentityAliasValueConverter : IIdentityValueConverter + { + private readonly string[] _domains; + + private readonly IIdentityManagementService _identityManagementService; + + private readonly string _tenantId; + + /// + /// Initializes a new instance of the class. + /// + /// An instance of to perform lookups. + /// In a hosted VSTS instance, the tenantId to scope identity searches. + /// A set of domains used to create s for search. + /// + /// var ims = ...; + /// var mapper = new IdentityAliasMapper(ims, "CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C", "contoso.com"); + /// + public IdentityAliasValueConverter( + IIdentityManagementService identityManagementService, + string tenantId, + params string[] domains) + { + if (domains == null) throw new ArgumentNullException(nameof(domains)); + if (string.IsNullOrEmpty(tenantId)) throw new ArgumentException("Value cannot be null or empty.", nameof(tenantId)); + if (domains.Length == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(domains)); + _identityManagementService = identityManagementService ?? throw new ArgumentNullException(nameof(identityManagementService)); + _tenantId = tenantId; + _domains = domains; + } + + /// + /// Converts the specified to an . + /// + /// The value. + /// An instance whose value is equivilent to the value of . + /// "danj" becomes "danj@contoso.com" + public object Map(object value) + { + if (value is string stringValue) return GetIdentityNames(stringValue).Single(); + + if (value is IEnumerable stringArray) return GetIdentityNames(stringArray.ToArray()); + + return value; + } + + private string[] GetIdentityNames(params string[] aliases) + { + var identities = _identityManagementService.GetIdentityForAliases(aliases.ToList(), _tenantId, _domains); + return identities.Select(i => i.Value.GetIdentityName() ?? i.Key).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity/Linq/Visitors/IIdentityMapper.cs b/src/Qwiq.Identity/Linq/Visitors/IIdentityMapper.cs deleted file mode 100644 index 17d96113..00000000 --- a/src/Qwiq.Identity/Linq/Visitors/IIdentityMapper.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Microsoft.Qwiq.Identity.Linq.Visitors -{ - public interface IIdentityMapper - { - object Map(object value); - } -} diff --git a/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs b/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs deleted file mode 100644 index 072d4bb3..00000000 --- a/src/Qwiq.Identity/Linq/Visitors/IdentityMapper.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Qwiq.Identity.Linq.Visitors -{ - public class IdentityMapper : IIdentityMapper - { - private readonly IIdentityManagementService _identityManagementService; - private readonly string _tenantId; - private readonly string[] _domains; - - public IdentityMapper(IIdentityManagementService identityManagementService, string tenantId, params string[] domains) - { - _identityManagementService = identityManagementService; - _tenantId = tenantId; - _domains = domains; - } - - private string[] GetDisplayNames(params string[] aliases) - { - var identities = _identityManagementService.GetIdentityForAliases(aliases.ToList(), _tenantId, _domains); - return identities.Select(i => i.Value?.DisplayName ?? i.Key).ToArray(); - } - - public object Map(object value) - { - if (value is string stringValue) - { - return GetDisplayNames(stringValue).Single(); - } - - if (value is IEnumerable stringArray) - { - return GetDisplayNames(stringArray.ToArray()); - } - - return value; - } - } -} diff --git a/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs b/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs deleted file mode 100644 index f7bd69ec..00000000 --- a/src/Qwiq.Identity/Linq/Visitors/IdentityVisitor.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Microsoft.Qwiq.Identity.Attributes; - -namespace Microsoft.Qwiq.Identity.Linq.Visitors -{ - public class IdentityVisitor : ExpressionVisitor - { - private readonly IIdentityMapper _mapper; - private bool _needsIdentityMapping; - - public IdentityVisitor(IIdentityMapper mapper) - { - _mapper = mapper; - } - - protected override Expression VisitBinary(BinaryExpression node) - { - _needsIdentityMapping = NeedsIdentityMapping(new[] { node.Left, node.Right }); - - var newNode = base.VisitBinary(node); - _needsIdentityMapping = false; - - return newNode; - } - - protected override Expression VisitConstant(ConstantExpression node) - { - if (!_needsIdentityMapping) - { - return node; - } - - var newNode = _mapper.Map(node.Value); - return Expression.Constant(newNode); - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - _needsIdentityMapping = NeedsIdentityMapping(node.Arguments); - - var newNode = base.VisitMethodCall(node); - _needsIdentityMapping = false; - - return newNode; - } - - private static bool IsIdentityField(Type type, string propertyName) - { - var property = type.GetProperty(propertyName); - - var customAttributes = Enumerable.Empty(); - if (property != null) - { - customAttributes = - property.GetCustomAttributes(typeof(IdentityFieldAttribute), true) - .Cast(); - } - return customAttributes.Any(); - } - - private static bool NeedsIdentityMapping(IEnumerable expressions) - { - return expressions.OfType().Any(arg => IsIdentityField(arg.Expression.Type, arg.Member.Name)); - } - } -} - diff --git a/src/Qwiq.Identity/Mapper/WorkItemField.cs b/src/Qwiq.Identity/Mapper/WorkItemField.cs deleted file mode 100644 index 325769fd..00000000 --- a/src/Qwiq.Identity/Mapper/WorkItemField.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Microsoft.Qwiq.Identity.Mapper -{ - internal struct WorkItemField - { - public string Name; - public string Value; - } -} diff --git a/src/Qwiq.Identity/Properties/AssemblyInfo.cs b/src/Qwiq.Identity/Properties/AssemblyInfo.cs index 0d54ad76..33da84ed 100644 --- a/src/Qwiq.Identity/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Identity/Properties/AssemblyInfo.cs @@ -7,20 +7,9 @@ // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Identity")] [assembly: AssemblyDescription("Provides identity extension methods on top of Microsoft.Qwiq.Core")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("648010b4-a84b-4ae0-81f6-2de6cf16de69")] -[assembly: InternalsVisibleTo("Qwiq.Identity.Tests")] - +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Integration.Tests")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Tests")] diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 292b9d23..a76582b2 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -1,6 +1,6 @@  - + Debug @@ -45,14 +45,13 @@ - + + Properties\AssemblyInfo.Common.cs + + - - - - - - + + @@ -61,9 +60,6 @@ - - ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll - ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -86,7 +82,6 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - @@ -94,10 +89,6 @@ {8AC61B6E-BEC1-482D-A043-C65D2D343B35} Qwiq.Core - - {016E8D93-4195-4639-BCD5-77633E8E1681} - Qwiq.Mapper - @@ -106,6 +97,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index 056504f8..b3090d6a 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -1,9 +1,8 @@  - - + diff --git a/src/Qwiq.Linq.Identity/Properties/AssemblyInfo.cs b/src/Qwiq.Linq.Identity/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..9b4ff400 --- /dev/null +++ b/src/Qwiq.Linq.Identity/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Qwiq.Linq.Identity")] +[assembly: AssemblyDescription("Extensions of Microsoft.Qwiq.Linq providing identity assistance in LINQ.")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0451d5ea-0206-48a6-a759-dc6572c4cd39")] \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj new file mode 100644 index 00000000..c6178156 --- /dev/null +++ b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj @@ -0,0 +1,84 @@ + + + + + + Debug + AnyCPU + {0451D5EA-0206-48A6-A759-DC6572C4CD39} + Library + Properties + Microsoft.Qwiq.Linq + Microsoft.Qwiq.Linq.Identity + v4.6 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + Off + bin\Debug\Microsoft.Qwiq.Linq.Identity.xml + true + ..\..\build\rulesets\ship.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Microsoft.Qwiq.Linq.Identity.xml + true + Off + ..\..\build\rulesets\ship.ruleset + true + + + + + + + + + Properties\AssemblyInfo.Common.cs + + + + + + + {B3654D2D-B4D4-405C-9AEC-FC1859A87E74} + Qwiq.Identity + + + {1edeb333-3084-42bd-b273-4009b4b18541} + Qwiq.Linq + + + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1} + Qwiq.Mapper.Identity + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.nuspec b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.nuspec new file mode 100644 index 00000000..c82f9b7b --- /dev/null +++ b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.nuspec @@ -0,0 +1,16 @@ + + + + $id$ + $version$ + $title$ + Microsoft + Microsoft, QWIQ + https://github.com/MicrosoftEdge/Microsoft.Qwiq + https://github.com/MicrosoftEdge/Microsoft.Qwiq/blob/master/LICENSE + true + $description$ + $copyright$ + Microsoft Team Foundation Server TFS VSO Visual Studio Online VisualStudio Agile WIT Work Item Tracking Object Model VSTS TeamFoundation TFSOM + + \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs b/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs new file mode 100644 index 00000000..9a6180c3 --- /dev/null +++ b/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +using Microsoft.Qwiq.Identity; +using Microsoft.Qwiq.Mapper.Attributes; + +namespace Microsoft.Qwiq.Linq.Visitors +{ + /// + /// Represents a visitor used to detect and rewrite identitiy values in the expression tree. + /// + /// + public class IdentityMappingVisitor : IdentityComboStringVisitor + { + private readonly IIdentityValueConverter _valueConverter; + + private bool _needsIdentityMapping; + + /// + /// Initializes a new instance of the class. + /// + /// An instance of used to convert identity values. + public IdentityMappingVisitor(IIdentityValueConverter valueConverter) + { + _valueConverter = valueConverter ?? throw new ArgumentNullException(nameof(valueConverter)); + } + + /// + /// Gets a value indicating whether the visited expression needs identity mapping. + /// + /// true if the expression needs identity mapping; otherwise, false. + protected override bool NeedsIdentityMapping => _needsIdentityMapping || base.NeedsIdentityMapping; + + /// + /// Visits the children of the . + /// + /// The expression to visit. + /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. + protected override Expression VisitBinary(BinaryExpression node) + { + _needsIdentityMapping = ExpressionsNeedIdentityMapping(new[] { node.Left, node.Right }); + + var newNode = base.VisitBinary(node); + _needsIdentityMapping = false; + + return newNode; + } + + /// + /// Visits the . + /// + /// The expression to visit. + /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. + protected override Expression VisitConstant(ConstantExpression node) + { + if (!NeedsIdentityMapping) return base.VisitConstant(node); + + var newNode = _valueConverter.Map(node.Value); + return Expression.Constant(newNode); + } + + /// + /// Visits the children of the . + /// + /// The expression to visit. + /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. + protected override Expression VisitMethodCall(MethodCallExpression node) + { + _needsIdentityMapping = ExpressionsNeedIdentityMapping(node.Arguments); + + var newNode = base.VisitMethodCall(node); + _needsIdentityMapping = false; + + return newNode; + } + + private static bool IsIdentityField(Type type, string propertyName) + { + var property = type.GetProperty(propertyName); + + var customAttributes = Enumerable.Empty(); + if (property != null) + customAttributes = property.GetCustomAttributes(typeof(IdentityFieldAttribute), true).Cast(); + return customAttributes.Any(); + } + + private bool ExpressionsNeedIdentityMapping(IEnumerable expressions) + { + return expressions.OfType().Any(arg => IsIdentityField(arg.Expression.Type, arg.Member.Name)) + || base.NeedsIdentityMapping; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/app.config b/src/Qwiq.Linq.Identity/app.config new file mode 100644 index 00000000..c83509f5 --- /dev/null +++ b/src/Qwiq.Linq.Identity/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/packages.config b/src/Qwiq.Linq.Identity/packages.config new file mode 100644 index 00000000..9c16c4cc --- /dev/null +++ b/src/Qwiq.Linq.Identity/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Qwiq.Linq/Properties/AssemblyInfo.cs b/src/Qwiq.Linq/Properties/AssemblyInfo.cs index 2fc5783e..2e2a2d66 100644 --- a/src/Qwiq.Linq/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Linq/Properties/AssemblyInfo.cs @@ -5,18 +5,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Linq")] -[assembly: AssemblyDescription("Add a LINQ query provider to simplify querying for work items")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyDescription("A LINQ query provider to simplify querying for work items")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("7910c9a5-d57e-4f94-bb4a-69f968407320")] diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index b7417e90..2e305e4b 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -1,6 +1,6 @@  - + Debug @@ -45,6 +45,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -70,7 +73,7 @@ - + @@ -107,6 +110,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Linq/Visitors/IdentityVisitor.cs b/src/Qwiq.Linq/Visitors/IdentityComboStringVisitor.cs similarity index 54% rename from src/Qwiq.Linq/Visitors/IdentityVisitor.cs rename to src/Qwiq.Linq/Visitors/IdentityComboStringVisitor.cs index eefbd467..d3010464 100644 --- a/src/Qwiq.Linq/Visitors/IdentityVisitor.cs +++ b/src/Qwiq.Linq/Visitors/IdentityComboStringVisitor.cs @@ -5,13 +5,21 @@ namespace Microsoft.Qwiq.Linq.Visitors { - public class IdentityVisitor : ExpressionVisitor + /// + /// Represents a visitor to detect identity values in the expression tree. + /// + /// + public class IdentityComboStringVisitor : ExpressionVisitor { private static readonly HashSet IdentityProperties; - private bool _needsIdentityMapping; + /// + /// Gets a value indicating whether the visited expression needs identity mapping. + /// + /// true if the expression needs identity mapping; otherwise, false. + protected virtual bool NeedsIdentityMapping { get; private set; } - static IdentityVisitor() + static IdentityComboStringVisitor() { IdentityProperties = new HashSet(Comparer.OrdinalIgnoreCase) @@ -28,19 +36,29 @@ static IdentityVisitor() }; } + /// + /// Visits the children of the . + /// + /// The expression to visit. + /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. protected override Expression VisitBinary(BinaryExpression node) { - _needsIdentityMapping = NeedsIdentityMapping(new[] { node.Left, node.Right }); + NeedsIdentityMapping = ExpressionsNeedIdentityMapping(new[] { node.Left, node.Right }); var newNode = base.VisitBinary(node); - _needsIdentityMapping = false; + NeedsIdentityMapping = false; return newNode; } + /// + /// Visits the . + /// + /// The expression to visit. + /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. protected override Expression VisitConstant(ConstantExpression node) { - if (!_needsIdentityMapping) return base.VisitConstant(node); + if (!NeedsIdentityMapping) return base.VisitConstant(node); void Validate(string identity) { @@ -59,18 +77,23 @@ void Validate(string identity) return base.VisitConstant(node); } + /// + /// Visits the children of the . + /// + /// The expression to visit. + /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. protected override Expression VisitMethodCall(MethodCallExpression node) { - _needsIdentityMapping = NeedsIdentityMapping(node.Arguments); + NeedsIdentityMapping = ExpressionsNeedIdentityMapping(node.Arguments); // TODO: Support cases: item["Assigned To"] and item.Fields["Assigned To"] var newNode = base.VisitMethodCall(node); - _needsIdentityMapping = false; + NeedsIdentityMapping = false; return newNode; } - private static bool NeedsIdentityMapping(IEnumerable expressions) + private static bool ExpressionsNeedIdentityMapping(IEnumerable expressions) { return expressions.OfType().Any(arg => IdentityProperties.Contains(arg.Member.Name)); } diff --git a/src/Qwiq.Linq/Visitors/LocalCollectionExpander.cs b/src/Qwiq.Linq/Visitors/LocalCollectionExpander.cs index 903832c2..9591ec8d 100644 --- a/src/Qwiq.Linq/Visitors/LocalCollectionExpander.cs +++ b/src/Qwiq.Linq/Visitors/LocalCollectionExpander.cs @@ -9,6 +9,7 @@ namespace Microsoft.Qwiq.Linq.Visitors /// /// Enables cache key support for local collection values. /// + [Obsolete("This type has been deprecated and will be removed in a future version.")] public class LocalCollectionExpander : ExpressionVisitor { public static Expression Rewrite(Expression expression) diff --git a/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs b/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs index 1660548d..4c7e4593 100644 --- a/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs +++ b/src/Qwiq.Linq/Visitors/WiqlQueryBuilder.cs @@ -15,7 +15,7 @@ public class WiqlQueryBuilder : IWiqlQueryBuilder /// /// The translator used to visit the tree. public WiqlQueryBuilder(IWiqlTranslator translator) - : this(translator, new PartialEvaluator(), new IdentityVisitor(), new QueryRewriter()) + : this(translator, new PartialEvaluator(), new IdentityComboStringVisitor(), new QueryRewriter()) { } diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index afeba8ad..9c16c4cc 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/Attributes/IdentityFieldAttribute.cs b/src/Qwiq.Mapper.Identity/Attributes/IdentityFieldAttribute.cs new file mode 100644 index 00000000..08b35dae --- /dev/null +++ b/src/Qwiq.Mapper.Identity/Attributes/IdentityFieldAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Microsoft.Qwiq.Mapper.Attributes +{ + /// + /// Represents a class designating a property as an identity value. + /// + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class IdentityFieldAttribute : Attribute + { + } +} diff --git a/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs similarity index 80% rename from src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs rename to src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs index d074de1c..675faaf4 100644 --- a/src/Qwiq.Identity/Mapper/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs @@ -2,25 +2,40 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Microsoft.Qwiq.Identity.Attributes; -using Microsoft.Qwiq.Mapper; -using Microsoft.Qwiq.Mapper.Attributes; using FastMember; -namespace Microsoft.Qwiq.Identity.Mapper +using Microsoft.Qwiq.Identity; +using Microsoft.Qwiq.Mapper.Attributes; + +namespace Microsoft.Qwiq.Mapper { + /// + /// Class BulkIdentityAwareAttributeMapperStrategy. + /// + /// public class BulkIdentityAwareAttributeMapperStrategy : WorkItemMapperStrategyBase { private readonly IPropertyInspector _inspector; private readonly IIdentityManagementService _identityManagementService; + /// + /// Initializes a new instance of the class. + /// + /// The inspector. + /// The identity management service. public BulkIdentityAwareAttributeMapperStrategy(IPropertyInspector inspector, IIdentityManagementService identityManagementService) { _inspector = inspector; _identityManagementService = identityManagementService; } + /// + /// Maps the specified targe work item type. + /// + /// Type of the targe work item. + /// The work item mappings. + /// The work item mapper. public override void Map(Type targeWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Default); @@ -28,7 +43,7 @@ public override void Map(Type targeWorkItemType, IEnumerable GetIdentityMap(IIdentityManagementSe return ims.GetAliasesForDisplayNames(searchTerms.ToArray()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value.FirstOrDefault()); } - internal static ICollection GetWorkItemsWithIdentityFieldValues(IEnumerable workItems, System.Collections.Generic.IReadOnlyCollection witFieldNames) + private static ICollection GetWorkItemsWithIdentityFieldValues( + IEnumerable workItems, + System.Collections.Generic.IReadOnlyCollection witFieldNames) { return workItems.Select( diff --git a/src/Qwiq.Mapper.Identity/Properties/AssemblyInfo.cs b/src/Qwiq.Mapper.Identity/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..9a8085a6 --- /dev/null +++ b/src/Qwiq.Mapper.Identity/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Qwiq.Mapper.Identity")] +[assembly: AssemblyDescription("An extension of Microsoft.Qwiq.Mapper providing support for identity values.")] + + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("be25cf2d-fa53-4455-85b1-4eec1d979fb1")] + +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Tests")] diff --git a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj new file mode 100644 index 00000000..98943805 --- /dev/null +++ b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj @@ -0,0 +1,95 @@ + + + + + + Debug + AnyCPU + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1} + Library + Properties + Microsoft.Qwiq.Mapper + Microsoft.Qwiq.Mapper.Identity + v4.6 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + Off + bin\Debug\Microsoft.Qwiq.Mapper.Identity.xml + ..\..\build\rulesets\ship.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + Off + bin\Release\Microsoft.Qwiq.Mapper.Identity.xml + ..\..\build\rulesets\ship.ruleset + true + + + + ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll + + + + + + + + + + + + + Properties\AssemblyInfo.Common.cs + + + + + + + + + + {8AC61B6E-BEC1-482D-A043-C65D2D343B35} + Qwiq.Core + + + {B3654D2D-B4D4-405C-9AEC-FC1859A87E74} + Qwiq.Identity + + + {016E8D93-4195-4639-BCD5-77633E8E1681} + Qwiq.Mapper + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.nuspec b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.nuspec new file mode 100644 index 00000000..c82f9b7b --- /dev/null +++ b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.nuspec @@ -0,0 +1,16 @@ + + + + $id$ + $version$ + $title$ + Microsoft + Microsoft, QWIQ + https://github.com/MicrosoftEdge/Microsoft.Qwiq + https://github.com/MicrosoftEdge/Microsoft.Qwiq/blob/master/LICENSE + true + $description$ + $copyright$ + Microsoft Team Foundation Server TFS VSO Visual Studio Online VisualStudio Agile WIT Work Item Tracking Object Model VSTS TeamFoundation TFSOM + + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/WorkItemField.cs b/src/Qwiq.Mapper.Identity/WorkItemField.cs new file mode 100644 index 00000000..db286d8f --- /dev/null +++ b/src/Qwiq.Mapper.Identity/WorkItemField.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Microsoft.Qwiq.Mapper +{ + [StructLayout(LayoutKind.Sequential, Size = 1)] + internal struct WorkItemField + { + public string Name; + public string Value; + } +} diff --git a/src/Qwiq.Identity/Mapper/WorkItemWithFields.cs b/src/Qwiq.Mapper.Identity/WorkItemWithFields.cs similarity index 62% rename from src/Qwiq.Identity/Mapper/WorkItemWithFields.cs rename to src/Qwiq.Mapper.Identity/WorkItemWithFields.cs index f5abc23d..f3f0eee7 100644 --- a/src/Qwiq.Identity/Mapper/WorkItemWithFields.cs +++ b/src/Qwiq.Mapper.Identity/WorkItemWithFields.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.Runtime.InteropServices; -namespace Microsoft.Qwiq.Identity.Mapper +namespace Microsoft.Qwiq.Mapper { + [StructLayout(LayoutKind.Sequential, Size = 1)] internal struct WorkItemWithFields { public IWorkItem WorkItem { get; set; } diff --git a/src/Qwiq.Mapper.Identity/app.config b/src/Qwiq.Mapper.Identity/app.config new file mode 100644 index 00000000..c83509f5 --- /dev/null +++ b/src/Qwiq.Mapper.Identity/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/packages.config b/src/Qwiq.Mapper.Identity/packages.config new file mode 100644 index 00000000..469b914c --- /dev/null +++ b/src/Qwiq.Mapper.Identity/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper/Properties/AssemblyInfo.cs b/src/Qwiq.Mapper/Properties/AssemblyInfo.cs index 9fd6a2c6..4d2ce780 100644 --- a/src/Qwiq.Mapper/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Mapper/Properties/AssemblyInfo.cs @@ -6,17 +6,6 @@ // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Mapper")] [assembly: AssemblyDescription("Adds mapping / translation between the Qwiq work items and your own model types")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("6ee68fb3-44b3-40e5-ae64-5630cf246948")] diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 4bf28d7b..13232dea 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -1,6 +1,6 @@  - + Debug @@ -49,6 +49,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -90,6 +93,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index c1f8b027..469b914c 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/test/Qwiq.Benchmark/Properties/AssemblyInfo.cs b/test/Qwiq.Benchmark/Properties/AssemblyInfo.cs index ea709128..02768a14 100644 --- a/test/Qwiq.Benchmark/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Benchmark/Properties/AssemblyInfo.cs @@ -1,22 +1,10 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Qwiq.Benchmark")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.Benchmark")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Microsoft.Qwiq.Benchmark")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("d9ed32d7-03fa-468b-ad1a-249cef9c6cdb")] diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index f258dbe9..9aa2f4bd 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -1,7 +1,7 @@  + - Debug AnyCPU @@ -29,6 +29,7 @@ true 1591 + true @@ -142,6 +143,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -171,9 +175,9 @@ - + diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index 474617c1..21161785 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -9,7 +9,7 @@ - + diff --git a/test/Qwiq.Core.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Core.Tests/Properties/AssemblyInfo.cs index ebb4f594..ce4a1ad6 100644 --- a/test/Qwiq.Core.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Core.Tests/Properties/AssemblyInfo.cs @@ -1,22 +1,11 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Qwiq.UnitTests")] [assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.UnitTests")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("1106b0da-16b3-4b99-bea6-2a95c054b5e2")] diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj index 713f1dce..9ee18a3f 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.Tests.csproj @@ -1,7 +1,7 @@  + - Debug AnyCPU @@ -28,6 +28,7 @@ true true 1591 + true @@ -213,18 +214,18 @@ + + Properties\AssemblyInfo.Common.cs + - - - @@ -278,10 +279,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index ab9526b4..c228fd12 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -4,7 +4,7 @@ - + diff --git a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs index 267383c1..dd16bf8d 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs +++ b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs @@ -5,7 +5,6 @@ using BenchmarkDotNet.Running; using Microsoft.Qwiq.Benchmark; -using Microsoft.Qwiq.Identity.Mapper; using Microsoft.Qwiq.Mapper; using Microsoft.Qwiq.Mapper.Attributes; using Microsoft.Qwiq.Mocks; diff --git a/test/Qwiq.Identity.Benchmark.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Identity.Benchmark.Tests/Properties/AssemblyInfo.cs index 61d68575..a3e7abf0 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Identity.Benchmark.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,11 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Qwiq.Identity.Benchmark.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.Identity.Benchmark.Tests")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Microsoft.Qwiq.Identity.Benchmark.Tests")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("26dfd8e0-c486-4eae-ba7f-277a40af5cc2")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj index 3a8cc080..e335708a 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.Benchmark.Tests.csproj @@ -1,7 +1,7 @@  + - Debug @@ -23,6 +23,7 @@ true true 1591 + true @@ -134,6 +135,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -159,6 +163,10 @@ {016E8D93-4195-4639-BCD5-77633E8E1681} Qwiq.Mapper + + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1} + Qwiq.Mapper.Identity + {DB07E690-4B77-414F-91C7-1A48C9F01F24} Qwiq.Mocks @@ -184,9 +192,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/packages.config b/test/Qwiq.Identity.Benchmark.Tests/packages.config index 918b71f2..da6e7d41 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/packages.config +++ b/test/Qwiq.Identity.Benchmark.Tests/packages.config @@ -3,10 +3,11 @@ + - + diff --git a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs index 74fe3f07..8e50ab14 100644 --- a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs +++ b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs @@ -4,7 +4,6 @@ using System.Reflection; using Microsoft.Qwiq; -using Microsoft.Qwiq.Identity.Mapper; using Microsoft.Qwiq.Mapper; using Microsoft.Qwiq.Mapper.Attributes; using Microsoft.Qwiq.Mocks; diff --git a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs b/test/Qwiq.Identity.Tests/IdentityManagementServiceProxyTests.cs similarity index 91% rename from test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs rename to test/Qwiq.Identity.Tests/IdentityManagementServiceProxyTests.cs index 0f87a2e7..2dd7ed22 100644 --- a/test/Qwiq.Core.Tests/IdentityManagementServiceProxyTests.cs +++ b/test/Qwiq.Identity.Tests/IdentityManagementServiceProxyTests.cs @@ -1,13 +1,17 @@ using System.Collections.Generic; using System.Linq; + using Microsoft.Qwiq.Core.Tests.Mocks; -using Microsoft.Qwiq.Soap; +using Microsoft.Qwiq.Identity.Soap; using Microsoft.Qwiq.Tests.Common; using Microsoft.TeamFoundation.Framework.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Should; -namespace Microsoft.Qwiq.Core.Tests +using MockIdentityDescriptor = Microsoft.Qwiq.Core.Tests.Mocks.MockIdentityDescriptor; + +namespace Microsoft.Qwiq.Identity.Soap.Tests { public abstract class IdentityManagementServiceProxyTests : ContextSpecification { diff --git a/test/Qwiq.Identity.Tests/IdentityMapperTests.cs b/test/Qwiq.Identity.Tests/IdentityMapperTests.cs index e46bf061..06dc3e2c 100644 --- a/test/Qwiq.Identity.Tests/IdentityMapperTests.cs +++ b/test/Qwiq.Identity.Tests/IdentityMapperTests.cs @@ -1,8 +1,12 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; -using Should; using Microsoft.Qwiq; -using Microsoft.Qwiq.Identity.Linq.Visitors; +using Microsoft.Qwiq.Identity; + +using Should; + using Microsoft.Qwiq.Mocks; using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,26 +16,30 @@ namespace Qwiq.Identity.Tests [TestClass] public abstract class IdentityMapperTests : ContextSpecification { - protected IdentityMapper Instance { get; set; } + protected IdentityAliasValueConverter Instance { get; set; } protected T Input { get; set; } protected T ActualOutput { get; set; } protected T ExpectedOutput { get; set; } public override void Given() { Instance = - new IdentityMapper( - new MockIdentityManagementService(new Dictionary - { - {"alias", new MockTeamFoundationIdentity("An Alias", "alias")}, - {"other", new MockTeamFoundationIdentity("AnOther Alias", "other")} - }), "tenant", "domain"); + new IdentityAliasValueConverter( + new MockIdentityManagementService(new Dictionary + { + {"alias", new MockTeamFoundationIdentity(MockIdentityDescriptor.Create("alias", "domain", "tenant"), "An Alias", Guid.Empty)}, + {"other", new MockTeamFoundationIdentity(MockIdentityDescriptor.Create("other", "domain", "tenant"), "AnOther Alias", Guid.Empty)} + }), "tenant", "domain"); base.Given(); } public override void When() { - ActualOutput = (T)Instance.Map(Input); + var result = Instance.Map(Input); + + Debug.Print("Result: " + result.ToUsefulString()); + + ActualOutput = (T)result; } [TestMethod] @@ -48,7 +56,7 @@ public override void Given() { base.Given(); Input = "alias"; - ExpectedOutput = "An Alias"; + ExpectedOutput = "alias@domain"; } } @@ -69,8 +77,8 @@ public class when_a_stringarray_is_mapped_with_valid_identities : IdentityMapper public override void Given() { base.Given(); - Input = new [] { "alias", "other" }; - ExpectedOutput = new[] { "An Alias", "AnOther Alias" }; + Input = new[] { "alias", "other" }; + ExpectedOutput = new[] { "alias@domain", "other@domain" }; } } @@ -81,7 +89,7 @@ public override void Given() { base.Given(); Input = new[] { "alias", "noidentity2" }; - ExpectedOutput = new[] { "An Alias", "noidentity2" }; + ExpectedOutput = new[] { "alias@domain", "noidentity2" }; } } diff --git a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs index 40608f7f..53df57a2 100644 --- a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs +++ b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Qwiq; -using Microsoft.Qwiq.Identity.Attributes; using Microsoft.Qwiq.Mapper.Attributes; namespace Qwiq.Identity.Tests.Mocks diff --git a/test/Qwiq.Core.Tests/Mocks/MockIdentityDescriptor.cs b/test/Qwiq.Identity.Tests/Mocks/MockIdentityDescriptor.cs similarity index 100% rename from test/Qwiq.Core.Tests/Mocks/MockIdentityDescriptor.cs rename to test/Qwiq.Identity.Tests/Mocks/MockIdentityDescriptor.cs diff --git a/test/Qwiq.Core.Tests/Mocks/MockIdentityManagementService2.cs b/test/Qwiq.Identity.Tests/Mocks/MockIdentityManagementService2.cs similarity index 100% rename from test/Qwiq.Core.Tests/Mocks/MockIdentityManagementService2.cs rename to test/Qwiq.Identity.Tests/Mocks/MockIdentityManagementService2.cs diff --git a/test/Qwiq.Identity.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Identity.Tests/Properties/AssemblyInfo.cs index dcf12007..b260ab56 100644 --- a/test/Qwiq.Identity.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Identity.Tests/Properties/AssemblyInfo.cs @@ -5,19 +5,7 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Qwiq.Identity.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.Identity.Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Microsoft.Qwiq.Identity.Tests")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("ce68530e-eb8f-4be2-9563-a09ac70ea8c1")] diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj index 4d744563..c6fbe7b3 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.Tests.csproj @@ -1,15 +1,15 @@  + - Debug AnyCPU {CE68530E-EB8F-4BE2-9563-A09AC70EA8C1} Library Properties - Qwiq.Identity.Tests - Qwiq.Identity.Tests + Microsoft.Qwiq.Identity.Tests + Microsoft.Qwiq.Identity.Tests v4.6 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -41,22 +41,176 @@ TRACE + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + + ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Build.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Client.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Dashboards.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.DeleteTeamProject.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Diff.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Discussion.Client.dll + + + ..\..\packages\Microsoft.TeamFoundation.DistributedTask.Common.15.112.1\lib\net45\Microsoft.TeamFoundation.DistributedTask.Common.Contracts.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Git.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.TestIntegration.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.ProjectManagement.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.SharePointReporting.Integration.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestImpact.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Client.dll + True + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.Common.dll + True + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.VersionControl.Common.Integration.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Common.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.ExtendedClient.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll + + + ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.InteractiveClient.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Client.Interactive.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.Common.dll + + + ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll + ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll ..\..\packages\MSTest.TestFramework.1.1.14\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + ..\..\packages\Should.1.1.20\lib\Should.dll + + ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll + + + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + + + + ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + + + Properties\AssemblyInfo.Common.cs + + + + @@ -68,10 +222,18 @@ {b3654d2d-b4d4-405c-9aec-fc1859a87e74} Qwiq.Identity + + {2b588d8c-5e01-4b48-96a7-b961fc54a4ac} + Qwiq.Identity.Soap + {016E8D93-4195-4639-BCD5-77633E8E1681} Qwiq.Mapper + + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1} + Qwiq.Mapper.Identity + {db07e690-4b77-414f-91c7-1a48c9f01f24} Qwiq.Mocks @@ -93,9 +255,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/app.config b/test/Qwiq.Identity.Tests/app.config index 871b1c0f..43500b67 100644 --- a/test/Qwiq.Identity.Tests/app.config +++ b/test/Qwiq.Identity.Tests/app.config @@ -32,4 +32,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index 7195e387..c228fd12 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -1,8 +1,20 @@  - + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/GlobalSuppressions.cs b/test/Qwiq.Integration.Tests/GlobalSuppressions.cs index f68dc3f5dde6b32cce4833031a4cfa2fc136b0c7..cb5d10da22df7675cf913abb8a1fe2079ae879c6 100644 GIT binary patch delta 463 zcmb_Xu}%U(6r2kNFkW-S!cgE3P)R{T2iTYxB2hwNaXGmKF5H3Mt%Zd>1tB48V&PAi z+z(J%@If>t&OT$Kt;N3GH#=`;-nRbIeb*J^mRDw5s5Q>eLKh1pxMzLD93h5$rMxp- zV=8FOt^-A8fXsLY&C(6=J3N`T`)u|__x5{ch6`x&63+T~pq`>q#2T_EpY@WQ9{PMO z=;otMZbr_8o#k2OAMOBG=ozQ{%B)LI(S0O|G%JCQjET(@*=Cky+S)b%p zukH+e8igF4GiJnN1C1r1UD1XfNi0pPNaERO2~SKU<-bM8 + public override void Given() + { + var wis = TimedAction(() => IntegrationSettings.CreateSoapStore(), "SOAP", "WIS Create"); + Instance = TimedAction(() => wis.GetIdentityManagementService(), "SOAP", "IMS Create"); + } + } + + [TestClass] + public class Given_a_valid_user_display_name : SoapIdentityManagementServiceContextSpecification + { + private IEnumerable _results; + + public override void When() + { + _results = TimedAction(() => Instance.ReadIdentities(IdentitySearchFactor.DisplayName, new[]{ "PETER LAVALLEE" }).First().Value, "SOAP", "AliasForDisplayName"); + + Debug.Print("Results: " + _results.EachToUsefulString()); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + public void result_contains_a_single_alias_PELAVALL() + { + _results.Single().GetUserAlias().ShouldEqual("pelavall"); + } + } + + [TestClass] + public class Given_a_valid_user_alias : SoapIdentityManagementServiceContextSpecification + { + private ITeamFoundationIdentity _results; + + public override void When() + { + _results = TimedAction(() => Instance.GetIdentityForAlias("pelavall", "72F988BF-86F1-41AF-91AB-2D7CD011DB47", "microsoft.com"), "SOAP", "AliasForDisplayName"); + + Debug.Print("Results: " + _results.ToUsefulString()); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + public void result_contains_a_single_alias_PELAVALL() + { + _results.GetUserAlias().ShouldEqual("pelavall"); + } + } +} diff --git a/test/Qwiq.Integration.Tests/IdentityMapperTests.cs b/test/Qwiq.Integration.Tests/IdentityMapperTests.cs new file mode 100644 index 00000000..cde7b2bb --- /dev/null +++ b/test/Qwiq.Integration.Tests/IdentityMapperTests.cs @@ -0,0 +1,110 @@ +using System.Linq; +using System.Linq.Expressions; + +using Microsoft.Qwiq.Identity; +using Microsoft.Qwiq.Identity.Soap; +using Microsoft.Qwiq.Linq; +using Microsoft.Qwiq.Linq.Visitors; +using Microsoft.Qwiq.Tests.Common; +using Microsoft.TeamFoundation.Framework.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Integration.Tests +{ + public abstract class SoapIdentityMapperContextSpecification : TimedContextSpecification + { + protected IdentityAliasValueConverter Instance { get; set; } + protected T Input { get; set; } + protected T ActualOutput { get; set; } + protected T ExpectedOutput { get; set; } + + /// + public override void Given() + { + base.Given(); + + var wis = TimedAction(() => IntegrationSettings.CreateSoapStore(), "SOAP", "WIS Create"); + var soapIms = TimedAction(() => wis.GetIdentityManagementService(), "SOAP", "IMS Create"); + Instance = new IdentityAliasValueConverter(soapIms, "72F988BF-86F1-41AF-91AB-2D7CD011DB47", "microsoft.com"); + } + + public override void When() + { + ActualOutput = TimedAction(() => (T)Instance.Map(Input), "SOAP", "Map"); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + public void the_actual_output_is_the_expected_output() + { + ActualOutput.ShouldEqual(ExpectedOutput); + } + } + + [TestClass] + public class when_a_string_is_mapped_with_a_valid_identity : SoapIdentityMapperContextSpecification + { + public override void Given() + { + base.Given(); + Input = "rimuri"; + ExpectedOutput = "rimuri@microsoft.com"; + } + } + + public abstract class IdentityMapperContextSpecification : WorkItemStoreComparisonContextSpecification + { + protected IOrderedQueryable SoapQueryable { get; private set; } + protected IOrderedQueryable RestQueryable { get; private set; } + + + + /// + public override void Given() + { + base.Given(); + + var soapIms = ((Soap.IInternalTeamProjectCollection)Soap.TeamProjectCollection) + .GetService() + .AsProxy(); + var translator = new WiqlTranslator(); + var idMapper = new IdentityAliasValueConverter(soapIms, "72F988BF-86F1-41AF-91AB-2D7CD011DB47", "microsoft.com"); + var visitors = new ExpressionVisitor[] + { + new PartialEvaluator(), + new IdentityMappingVisitor(idMapper), + new QueryRewriter() + }; + + var soapBuilder = new WiqlQueryBuilder(translator, visitors); + var soapQp = new TeamFoundationServerWorkItemQueryProvider(Soap, soapBuilder); + SoapQueryable = new Query(soapQp, soapBuilder); + + var restBuilder = new WiqlQueryBuilder(); + var restQp = new TeamFoundationServerWorkItemQueryProvider(Rest, restBuilder); + RestQueryable = new Query(restQp, restBuilder); + } + } + + [TestClass] + public class Given_WorkItems_queried_by_LINQ_on_AssignedTo_with_IdentityVisitor_by_alias : IdentityMapperContextSpecification + { + /// + public override void When() + { + RestResult.WorkItems = RestQueryable.Where(i => i.AssignedTo == "rimuri").ToArray().ToWorkItemCollection(); + SoapResult.WorkItems = SoapQueryable.Where(i => i.AssignedTo == "rimuri").ToArray().ToWorkItemCollection(); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + public void SOAP_returned_results() + { + SoapResult.WorkItems.Count.ShouldBeGreaterThan(0); + } + } +} diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index 7f2a59fc..43c8f1f7 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Qwiq.Credentials; -using Microsoft.Qwiq.Soap; namespace Microsoft.Qwiq.Integration.Tests { @@ -12,13 +11,13 @@ public static class IntegrationSettings public static Func CreateRestStore { get; } = () => { var options = RestOptions; - return WorkItemStoreFactory.Default.Create(options); + return Rest.WorkItemStoreFactory.Default.Create(options); }; public static Func CreateSoapStore { get; } = () => { var options = SoapOptions; - return WorkItemStoreFactory.Default.Create(options); + return Soap.WorkItemStoreFactory.Default.Create(options); }; public static AuthenticationOptions RestOptions { get; } = diff --git a/test/Qwiq.Integration.Tests/LinqTests.cs b/test/Qwiq.Integration.Tests/LinqTests.cs new file mode 100644 index 00000000..defc13ae --- /dev/null +++ b/test/Qwiq.Integration.Tests/LinqTests.cs @@ -0,0 +1,53 @@ +using System.Linq; + +using Microsoft.Qwiq.Linq; +using Microsoft.Qwiq.Linq.Visitors; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.Integration.Tests +{ + public abstract class LinqContextSpecification : WorkItemStoreComparisonContextSpecification + { + protected IOrderedQueryable SoapQueryable { get; private set; } + protected IOrderedQueryable RestQueryable { get; private set; } + + + + /// + public override void Given() + { + base.Given(); + + + var soapBuilder = new WiqlQueryBuilder(); + var soapQp = new TeamFoundationServerWorkItemQueryProvider(Soap, soapBuilder); + SoapQueryable = new Query(soapQp, soapBuilder); + + var restBuilder = new WiqlQueryBuilder(); + var restQp = new TeamFoundationServerWorkItemQueryProvider(Rest, restBuilder); + RestQueryable = new Query(restQp, restBuilder); + } + } + + [TestClass] + public class Given_WorkItems_queried_by_LINQ_on_AssignedTo_by_UPN : LinqContextSpecification + { + /// + public override void When() + { + RestResult.WorkItems = RestQueryable.Where(i => i.AssignedTo == "rimuri@microsoft.com").ToArray().ToWorkItemCollection(); + SoapResult.WorkItems = SoapQueryable.Where(i => i.AssignedTo == "rimuri@microsoft.com").ToArray().ToWorkItemCollection(); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void the_results_are_equal() + { + RestResult.WorkItems.ShouldContainOnly(SoapResult.WorkItems); + } + } +} diff --git a/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs index ab7f0664..9b403015 100644 --- a/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs @@ -5,14 +5,6 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Integration.Tests")] -[assembly: AssemblyDescription("Provides Quick Workitem Queries to TFS and Visual Studio Online")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Microsoft.Qwiq")] -[assembly: AssemblyCopyright("\x00a9 Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] [assembly: Guid("e4130432-c890-41e0-8407-c4142caf59d8")] diff --git a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj index 502e6b95..1d5b734f 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.Integration.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -203,11 +204,17 @@ + + Properties\AssemblyInfo.Common.cs + + + + @@ -241,6 +248,22 @@ {6f5ffc42-0539-4161-b348-a54adb57c2bd} Qwiq.Core.Soap + + {B3654D2D-B4D4-405C-9AEC-FC1859A87E74} + Qwiq.Identity + + + {2b588d8c-5e01-4b48-96a7-b961fc54a4ac} + Qwiq.Identity.Soap + + + {1EDEB333-3084-42BD-B273-4009B4B18541} + Qwiq.Linq + + + {0451D5EA-0206-48A6-A759-DC6572C4CD39} + Qwiq.Linq.Identity + {B45C92B0-AC36-409D-86A5-5428C87384C3} Qwiq.Tests.Common @@ -257,6 +280,7 @@ + diff --git a/test/Qwiq.Integration.Tests/packages.config b/test/Qwiq.Integration.Tests/packages.config index 3cfbc88b..c228fd12 100644 --- a/test/Qwiq.Integration.Tests/packages.config +++ b/test/Qwiq.Integration.Tests/packages.config @@ -4,6 +4,7 @@ + diff --git a/test/Qwiq.Linq.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Linq.Tests/Properties/AssemblyInfo.cs index 2ba86189..5fc6417e 100644 --- a/test/Qwiq.Linq.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Linq.Tests/Properties/AssemblyInfo.cs @@ -4,19 +4,8 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Qwiq.Linq.Tests")] +[assembly: AssemblyTitle("Microsoft.Qwiq.Linq.Tests")] [assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.Linq.Tests")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("0dd0ea3b-2abc-483c-ac1f-e4e4702d4da5")] diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj index fbd9c4d1..25a621a5 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.Tests.csproj @@ -1,7 +1,7 @@  + - Debug AnyCPU @@ -65,6 +65,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -117,9 +120,9 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index 7195e387..c542cc7f 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -1,7 +1,7 @@  - + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Mapper.Benchmark.Tests/Properties/AssemblyInfo.cs index 855eca9c..63a50538 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,10 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Qwiq.Mapper.Benchmark.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.Mapper.Benchmark.Tests")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Microsoft.Qwiq.Mapper.Benchmark.Tests")] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("db2c0ac6-cf65-412e-93a3-d3f51857bed9")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: Guid("db2c0ac6-cf65-412e-93a3-d3f51857bed9")] \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj index c41d9049..778c7598 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.Benchmark.Tests.csproj @@ -1,7 +1,7 @@  + - Debug @@ -144,6 +144,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -188,9 +191,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 2c0d1d49..3c54f0dd 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -3,10 +3,11 @@ + - + diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 99aec18d..19f2bf7e 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -58,11 +58,7 @@ public ITeamProjectCollection TeamProjectCollection public TimeZone TimeZone => _innerWorkItemStore.TimeZone; - public string UserAccountName => _innerWorkItemStore.UserAccountName; - - public string UserDisplayName => _innerWorkItemStore.UserDisplayName; - - public string UserSid => _innerWorkItemStore.UserSid; + public ITeamFoundationIdentity AuthorizedIdentity => _innerWorkItemStore?.AuthorizedIdentity; public IWorkItemLinkTypeCollection WorkItemLinkTypes { diff --git a/test/Qwiq.Mapper.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Mapper.Tests/Properties/AssemblyInfo.cs index accd0b31..79ccf4b4 100644 --- a/test/Qwiq.Mapper.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Mapper.Tests/Properties/AssemblyInfo.cs @@ -1,22 +1,10 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Qwiq.Mapper.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Qwiq.Mapper.Tests")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Microsoft.Qwiq.Mapper.Tests")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("b8f84c45-a73a-4e4e-8b2b-95f18966871d")] diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj index 00c14e2c..28c89e0e 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.Tests.csproj @@ -1,7 +1,7 @@  + - Debug AnyCPU @@ -82,6 +82,9 @@ + + Properties\AssemblyInfo.Common.cs + @@ -150,9 +153,9 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + 512 + true + prompt + 4 + + true + 1591 + Off + + bin\$(Configuration)\ + $(OutputPath) + + obj\$(Configuration)\ + False + + + true + + + + + true + full + false + DEBUG;TRACE + false + + + pdbonly + true + TRACE + + + \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj index 3c2e1394..4cd7afa5 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj @@ -1,44 +1,19 @@  + - Debug - AnyCPU {0F70E1C2-D696-4749-8601-374A7C9C268A} Library Properties Microsoft.Qwiq.Client.Rest Microsoft.Qwiq.Client.Rest v4.6 - 512 - true - ..\ - true - prompt - 4 - CS0108 ..\..\build\rulesets\ship.ruleset - true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\Microsoft.TeamFoundationServer.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index 7cb4c22b..bba52984 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -1,44 +1,19 @@  + - Debug - AnyCPU {6F5FFC42-0539-4161-B348-A54ADB57C2BD} Library Properties Microsoft.Qwiq.Client.Soap Microsoft.Qwiq.Client.Soap v4.6 - 512 - true - ..\ - true - prompt - 4 - CS0108 ..\..\build\rulesets\ship.ruleset - true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 0a15629a..8c285890 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -1,44 +1,20 @@  + - Debug - AnyCPU {8AC61B6E-BEC1-482D-A043-C65D2D343B35} Library Properties Microsoft.Qwiq Microsoft.Qwiq.Core v4.6 - 512 - true - ..\ - true - prompt - 4 - ..\..\build\rulesets\ship.ruleset true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj index 94a8680e..c295daa5 100644 --- a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj @@ -1,37 +1,19 @@  + - Debug - AnyCPU {2B588D8C-5E01-4B48-96A7-B961FC54A4AC} Library Properties Microsoft.Qwiq.Identity.Soap Microsoft.Qwiq.Identity.Soap v4.6 - 512 + ..\..\build\rulesets\ship.ruleset - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index ecb9294c..1bdbf876 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -1,46 +1,19 @@  + - Debug - AnyCPU {B3654D2D-B4D4-405C-9AEC-FC1859A87E74} Library Properties Microsoft.Qwiq.Identity Microsoft.Qwiq.Identity v4.6 - 512 - true - ..\ - true - prompt - 4 - CS0108 ..\..\build\rulesets\ship.ruleset - true - true - 1591 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - diff --git a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj index ed4ced52..3b0510bc 100644 --- a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj +++ b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj @@ -1,46 +1,19 @@  + - Debug - AnyCPU {0451D5EA-0206-48A6-A759-DC6572C4CD39} Library Properties Microsoft.Qwiq.Linq Microsoft.Qwiq.Linq.Identity v4.6 - 512 + ..\..\build\rulesets\ship.ruleset - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - Off - bin\Debug\Microsoft.Qwiq.Linq.Identity.xml - true - ..\..\build\rulesets\ship.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Microsoft.Qwiq.Linq.Identity.xml - true - Off - ..\..\build\rulesets\ship.ruleset - true - diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index 2e305e4b..b5b1f899 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -1,44 +1,19 @@  + - Debug - AnyCPU {1EDEB333-3084-42BD-B273-4009B4B18541} Library Properties Microsoft.Qwiq.Linq Microsoft.Qwiq.Linq v4.6 - 512 - true - ..\ - true - prompt - 4 - CS0108 ..\..\build\rulesets\ship.ruleset - true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - diff --git a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj index e2059d4b..f930740a 100644 --- a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj +++ b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj @@ -1,46 +1,20 @@  + - Debug - AnyCPU {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1} Library Properties Microsoft.Qwiq.Mapper Microsoft.Qwiq.Mapper.Identity v4.6 - 512 + ..\..\build\rulesets\ship.ruleset + $(OutputPath)Microsoft.Qwiq.Mapper.Identity.xml - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - Off - bin\Debug\Microsoft.Qwiq.Mapper.Identity.xml - ..\..\build\rulesets\ship.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - Off - bin\Release\Microsoft.Qwiq.Mapper.Identity.xml - ..\..\build\rulesets\ship.ruleset - true - ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 13232dea..7614e4f0 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -1,44 +1,19 @@  + - Debug - AnyCPU {016E8D93-4195-4639-BCD5-77633E8E1681} Library Properties Microsoft.Qwiq.Mapper Microsoft.Qwiq.Mapper v4.6 - 512 - true - ..\ - true - prompt - 4 - CS0108 ..\..\build\rulesets\ship.ruleset - true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\FastMember.1.1.0\lib\net40\FastMember.dll diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 7b532ba1..1830e097 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -1,10 +1,9 @@  + - Debug - AnyCPU {D9ED32D7-03FA-468B-AD1A-249CEF9C6CDB} Library Properties @@ -17,33 +16,10 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - 512 - true - ..\ - true - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\BenchmarkDotNet.0.10.4\lib\net46\BenchmarkDotNet.dll diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj index d2058bad..ad5784d2 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj @@ -1,10 +1,9 @@  + - Debug - AnyCPU {57407CCA-8444-4713-95E9-CFC1168D846B} Library Properties @@ -17,33 +16,10 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - 512 - true - ..\ - true - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - true - 1591 + - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj index abf30603..59cc656c 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj @@ -1,50 +1,26 @@  + - Debug - AnyCPU {26DFD8E0-C486-4EAE-BA7F-277A40AF5CC2} Library Properties Microsoft.Qwiq.Identity.BenchmarkTests Microsoft.Qwiq.Identity.BenchmarkTests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - true - ..\ - true - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - true - 1591 + - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\BenchmarkDotNet.0.10.4\lib\net46\BenchmarkDotNet.dll diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj index 2f1b99d0..f027697e 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj @@ -1,45 +1,25 @@  + - Debug - AnyCPU {CE68530E-EB8F-4BE2-9563-A09AC70EA8C1} Library Properties Microsoft.Qwiq.Identity Microsoft.Qwiq.Identity.UnitTests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - true - prompt - 4 ..\..\build\rulesets\noship.ruleset - true - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 2676a313..4f0ee7e0 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -1,47 +1,25 @@  + - Debug - AnyCPU {E4130432-C890-41E0-8407-C4142CAF59D8} Library Properties Microsoft.Qwiq Microsoft.Qwiq.IntegrationTests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + ..\..\build\rulesets\noship.ruleset - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - ..\..\packages\JetBrains.dotMemoryUnit.2.3.20160517.113140\lib\dotMemory.Unit.dll diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj index 111f7407..2dad5b0b 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj @@ -1,49 +1,25 @@  + - Debug - AnyCPU {0A26136B-A086-4CDF-BFF6-E0A83FB67446} Library Properties Microsoft.Qwiq.Linq Microsoft.Qwiq.Linq.UnitTests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + ..\..\build\rulesets\noship.ruleset - ..\ - true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -56,14 +32,6 @@ - - - - - - - - Properties\AssemblyInfo.Common.cs @@ -94,25 +62,6 @@ Qwiq.Tests.Common - - - - - - False - - - False - - - False - - - False - - - - diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj index f17583f7..83452b2a 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj @@ -1,45 +1,26 @@  + - Debug - AnyCPU {DB2C0AC6-CF65-412E-93A3-D3F51857BED9} Library Properties Microsoft.Qwiq.Mapper Microsoft.Qwiq.Mapper.BenchmarkTests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - prompt - 4 ..\..\build\rulesets\noship.ruleset - true - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\BenchmarkDotNet.0.10.4\lib\net46\BenchmarkDotNet.dll diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj index 7a2e6698..8894590b 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj @@ -1,49 +1,25 @@  + - Debug - AnyCPU {BECF2066-A6C5-475A-BA14-12890F284A12} Library Properties Microsoft.Qwiq.Mapper Microsoft.Qwiq.Mapper.UnitTests v4.6 - 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + ..\..\build\rulesets\noship.ruleset - ..\ - true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll @@ -73,14 +49,6 @@ - - - - - - - - Properties\AssemblyInfo.Common.cs @@ -128,24 +96,6 @@ Qwiq.Tests.Common - - - - - False - - - False - - - False - - - False - - - - @@ -159,11 +109,4 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index c2a7f626..66a8bb90 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -1,44 +1,19 @@  + - Debug - AnyCPU {DB07E690-4B77-414F-91C7-1A48C9F01F24} Library Properties Microsoft.Qwiq.Mocks Microsoft.Qwiq.Mocks v4.6 - 512 - true - ..\ - true - prompt - 4 - ..\..\build\rulesets\ship.ruleset - true - true - 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - false - - - pdbonly - true - bin\Release\ - TRACE - ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index 018fb464..554ee3fb 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -1,42 +1,20 @@  + - Debug - AnyCPU {B45C92B0-AC36-409D-86A5-5428C87384C3} Library Properties Microsoft.Qwiq.Tests.Common Microsoft.Qwiq.Tests.Common v4.6 - 512 + ..\..\build\rulesets\noship.ruleset - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - ..\..\build\rulesets\noship.ruleset - true - ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll From 675b5294a24c68a6aea8c8c283f6ae2951c5b0ac Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 10:14:25 -0700 Subject: [PATCH 152/251] Remove empty `` element --- test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 4f0ee7e0..0dc08fb6 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -267,7 +267,6 @@ Qwiq.Tests.Common - From 405a0d9dcf89e833cfd3a0e8a4cead2e056f685c Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 10:42:27 -0700 Subject: [PATCH 153/251] Upgrade BenchmarkDotNet packages --- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 16 ++++++++-------- test/Qwiq.Benchmark/packages.config | 8 ++++---- .../Qwiq.Identity.BenchmarkTests.csproj | 12 ++++++------ test/Qwiq.Identity.Benchmark.Tests/app.config | 2 +- .../packages.config | 6 +++--- .../Qwiq.Mapper.BenchmarkTests.csproj | 12 ++++++------ test/Qwiq.Mapper.Benchmark.Tests/app.config | 2 +- test/Qwiq.Mapper.Benchmark.Tests/packages.config | 6 +++--- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index 1830e097..f2e52dda 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -21,17 +21,17 @@ - - ..\..\packages\BenchmarkDotNet.0.10.4\lib\net46\BenchmarkDotNet.dll + + ..\..\packages\BenchmarkDotNet.0.10.5\lib\net46\BenchmarkDotNet.dll - - ..\..\packages\BenchmarkDotNet.Core.0.10.4\lib\net46\BenchmarkDotNet.Core.dll + + ..\..\packages\BenchmarkDotNet.Core.0.10.5\lib\net46\BenchmarkDotNet.Core.dll - - ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.4\lib\net46\BenchmarkDotNet.Diagnostics.Windows.dll + + ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.5\lib\net46\BenchmarkDotNet.Diagnostics.Windows.dll - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.4\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.5\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll ..\..\packages\Microsoft.CodeAnalysis.Common.2.1.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index 6ad9db8e..a041378f 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -1,9 +1,9 @@  - - - - + + + + diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj index 59cc656c..286d9de0 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj @@ -22,14 +22,14 @@ - - ..\..\packages\BenchmarkDotNet.0.10.4\lib\net46\BenchmarkDotNet.dll + + ..\..\packages\BenchmarkDotNet.0.10.5\lib\net46\BenchmarkDotNet.dll - - ..\..\packages\BenchmarkDotNet.Core.0.10.4\lib\net46\BenchmarkDotNet.Core.dll + + ..\..\packages\BenchmarkDotNet.Core.0.10.5\lib\net46\BenchmarkDotNet.Core.dll - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.4\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.5\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll ..\..\packages\Microsoft.CodeAnalysis.Common.2.1.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll diff --git a/test/Qwiq.Identity.Benchmark.Tests/app.config b/test/Qwiq.Identity.Benchmark.Tests/app.config index 6ead27bf..c8c0845a 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/app.config +++ b/test/Qwiq.Identity.Benchmark.Tests/app.config @@ -4,7 +4,7 @@ - + diff --git a/test/Qwiq.Identity.Benchmark.Tests/packages.config b/test/Qwiq.Identity.Benchmark.Tests/packages.config index 88b4ea5f..138d56cc 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/packages.config +++ b/test/Qwiq.Identity.Benchmark.Tests/packages.config @@ -1,8 +1,8 @@  - - - + + + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj index 83452b2a..29a26199 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj @@ -22,14 +22,14 @@ - - ..\..\packages\BenchmarkDotNet.0.10.4\lib\net46\BenchmarkDotNet.dll + + ..\..\packages\BenchmarkDotNet.0.10.5\lib\net46\BenchmarkDotNet.dll - - ..\..\packages\BenchmarkDotNet.Core.0.10.4\lib\net46\BenchmarkDotNet.Core.dll + + ..\..\packages\BenchmarkDotNet.Core.0.10.5\lib\net46\BenchmarkDotNet.Core.dll - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.4\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.5\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll ..\..\packages\Microsoft.CodeAnalysis.Common.2.1.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll diff --git a/test/Qwiq.Mapper.Benchmark.Tests/app.config b/test/Qwiq.Mapper.Benchmark.Tests/app.config index 03045999..862d0363 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/app.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/app.config @@ -24,7 +24,7 @@ - + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 8376dd14..2591e89b 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -1,8 +1,8 @@  - - - + + + From c59cd54b92f9ca44bf79ad7dd7417bd6eb4a3f39 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 11:09:49 -0700 Subject: [PATCH 154/251] Reinstall NuGet packages --- src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj | 2 +- src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj | 4 +- src/Qwiq.Core/Qwiq.Core.csproj | 2 +- .../Qwiq.Identity.Soap.csproj | 4 +- src/Qwiq.Identity/Qwiq.Identity.csproj | 3 +- .../Qwiq.Linq.Identity.csproj | 2 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 2 +- .../Qwiq.Mapper.Identity.csproj | 2 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 2 +- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 8 +- test/Qwiq.Benchmark/TraceEvent.ReadMe.txt | 79 ++++++++++++++++++ .../TraceEvent.ReleaseNotes.txt | 61 ++++++++++++++ .../_TraceEventProgrammersGuide.docx | Bin 0 -> 196963 bytes .../Qwiq.Core.UnitTests.csproj | 6 +- .../Qwiq.Identity.BenchmarkTests.csproj | 4 +- .../Qwiq.Identity.UnitTests.csproj | 6 +- .../Qwiq.IntegrationTests.csproj | 6 +- .../Qwiq.Linq.UnitTests.csproj | 2 +- .../Qwiq.Mapper.BenchmarkTests.csproj | 4 +- .../Qwiq.Mapper.UnitTests.csproj | 4 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 2 +- .../Qwiq.Tests.Common.csproj | 2 +- 22 files changed, 177 insertions(+), 30 deletions(-) create mode 100644 test/Qwiq.Benchmark/TraceEvent.ReadMe.txt create mode 100644 test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt create mode 100644 test/Qwiq.Benchmark/_TraceEventProgrammersGuide.docx diff --git a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj index 4cd7afa5..5a8a8059 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj @@ -1,7 +1,7 @@  - + {0F70E1C2-D696-4749-8601-374A7C9C268A} diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index bba52984..0f70507e 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -1,7 +1,7 @@  - + {6F5FFC42-0539-4161-B348-A54ADB57C2BD} @@ -235,8 +235,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 8c285890..da3444fc 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -1,7 +1,7 @@  - + {8AC61B6E-BEC1-482D-A043-C65D2D343B35} diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj index c295daa5..879469b4 100644 --- a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj @@ -1,7 +1,7 @@  - + {2B588D8C-5E01-4B48-96A7-B961FC54A4AC} @@ -201,8 +201,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 1bdbf876..0f933a88 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -1,7 +1,7 @@  - + {B3654D2D-B4D4-405C-9AEC-FC1859A87E74} @@ -56,6 +56,7 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + diff --git a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj index 3b0510bc..c1e19461 100644 --- a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj +++ b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj @@ -1,7 +1,7 @@  - + {0451D5EA-0206-48A6-A759-DC6572C4CD39} diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index b5b1f899..e4b3c152 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -1,7 +1,7 @@  - + {1EDEB333-3084-42BD-B273-4009B4B18541} diff --git a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj index f930740a..c863b0ab 100644 --- a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj +++ b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj @@ -1,7 +1,7 @@  - + {BE25CF2D-FA53-4455-85B1-4EEC1D979FB1} diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 7614e4f0..acfce312 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -1,7 +1,7 @@  - + {016E8D93-4195-4639-BCD5-77633E8E1681} diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index f2e52dda..a34895d3 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -1,8 +1,8 @@  - + {D9ED32D7-03FA-468B-AD1A-249CEF9C6CDB} Library @@ -111,6 +111,7 @@ ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll @@ -135,6 +136,7 @@ + @@ -148,6 +150,10 @@ + + + + diff --git a/test/Qwiq.Benchmark/TraceEvent.ReadMe.txt b/test/Qwiq.Benchmark/TraceEvent.ReadMe.txt new file mode 100644 index 00000000..f37f579e --- /dev/null +++ b/test/Qwiq.Benchmark/TraceEvent.ReadMe.txt @@ -0,0 +1,79 @@ + +************* Welcome to the Microsoft.Diagnostics.Tracing.TraceEvent library! *************** + +This library is designed to make controlling and parsing Event Tracing for Windows (ETW) events easy. +In particular if you are generating events with System.Diagnostics.Tracing.EventSource, this library +makes it easy to process that data. + +******** PROGRAMMERS GUIDE ******** + +If you are new to TraceEvent, see the _TraceEventProgammersGuide.docx that was installed as part of +your solution when this NuGet package was installed. + +************ FEEDBACK ************* + +If you have problems, wish to report a bug, or have a suggestion please log your comments on the +.NET Runtime Framework Blog http://blogs.msdn.com/b/dotnet/ under the TraceEvent announcement. + +********** RELEASE NOTES *********** + +If you are interested what particular features/bug fixes are in this particular version please +see the TraceEvent.RelaseNotes.txt file that is part of this package. It also contains +information about breaking changes (If you use the bcl.codeplex version in the past). + +************* SAMPLES ************* + +There is a companion NUGET package called Microsoft.Diagnostics.Tracing.TraceEvent.Samples. These +are simple but well commented examples of how to use this library. To get the samples, it is best +to simply create a new Console application, and then reference the Samples package from that App. +The package's README.TXT file tell you how to run the samples. + +************** BLOGS ************** + +See http://blogs.msdn.com/b/vancem/archive/tags/traceevent/ for useful blog entries on using this +package. + +*********** QUICK STARTS *********** + +The quick-starts below will get you going in a minimum of typing, but please see the WELL COMMENTED +samples in the Samples NUGET package that describe important background and other common scenarios. + +************************************************************************************************** +******* Quick Start: Turning on the 'MyEventSource' EventSource and log to MyEventsFile.etl: + + using (var session = new TraceEventSession("SimpleMontitorSession", "MyEventsFile.etl")) // Sessions collect and control event providers. Here we send data to a file + { + var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("MyEventSource"); // Get the unique ID for the eventSouce. + session.EnableProvider(eventSourceGuid); // Turn it on. + Thread.Sleep(10000); // Collect for 10 seconds then stop. + } + +************************************************************************************************** +******** Quick Start: Reading MyEventsFile.etl file and printing the events. + + using (var source = new ETWTraceEventSource("MyEtlFile.etl")) // Open the file + { + var parser = new DynamicTraceEventParser(source); // DynamicTraceEventParser knows about EventSourceEvents + parser.All += delegate(TraceEvent data) // Set up a callback for every event that prints the event + { + Console.WriteLine("GOT EVENT: " + data.ToString()); // Print the event. + }; + source.Process(); // Read the file, processing the callbacks. + } // Close the file. + + +************************************************************************************************************* +******** Quick Start: Turning on the 'MyEventSource', get callbacks in real time (no files involved). + + using (var session = new TraceEventSession("MyRealTimeSession")) // Create a session to listen for events + { + session.Source.Dynamic.All += delegate(TraceEvent data) // Set Source (stream of events) from session. + { // Get dynamic parser (knows about EventSources) + // Subscribe to all EventSource events + Console.WriteLine("GOT Event " + data); // Print each message as it comes in + }; + + var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("MyEventSource"); // Get the unique ID for the eventSouce. + session.EnableProvider(eventSourceGuid); // Enable MyEventSource. + session.Source.Process(); // Wait for incoming events (forever). + } diff --git a/test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt b/test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt new file mode 100644 index 00000000..21fcb5d0 --- /dev/null +++ b/test/Qwiq.Benchmark/TraceEvent.ReleaseNotes.txt @@ -0,0 +1,61 @@ +Version 1.0.0.3 - Initial release to NuGet, pre-release. + + TraceEvent has been available from the site http://bcl.codeplex.com/wikipage?title=TraceEvent for some time now + this NuGet Version of the library supersedes that one. WHile the 'core' part of the library is unchanged, + we did change lesser used features, and change the namespace and DLL name, which will cause break. We anticipate + it will take an hour or so to 'port' to this version from the old one. Below are specific details on what + has changed to help in this port. + + * The DLL has been renamed from TraceEvent.dll to Microsoft.Diagnostics.Tracing.TraceEvent.dll + * The name spaces for all classes have been changed. The easiest way to port is to simply place + the following using clauses at the top of any file that uses TraceEvent classes + using Microsoft.Diagnostics.Symbols; + using Microsoft.Diagnostics.Tracing; + using Microsoft.Diagnostics.Tracing.Etlx; + using Microsoft.Diagnostics.Tracing.Parsers.Clr; + using Microsoft.Diagnostics.Tracing.Parsers.Kernel; + using Microsoft.Diagnostics.Tracing.Session; + using Microsoft.Diagnostics.Tracing.Stacks; + * Any method with the name RelMSec in it has been changed to be RelativeMSec. The easiest port is to + simply globally rename RelMSec to RelativeMSec + * Any property in the Trace* classes that has the form Max*Index has been renamed to Count. + * A number of methods have been declared obsolete, these are mostly renames and the warning will tell you + how to update them. + * The following classes have been rename + SymPath -> SymbolPath + SymPathElement -> SymbolPathElement + SymbolReaderFlags -> SymbolReaderOptions + * TraceEventSession is now StopOnDispose (it will stop the session when TraceEventSesssion dies), by default + If you were relying on the kernel session living past the process that started it, you must now set + the StopOnDispose explicitly + * There used to be XmlAttrib extensions methods on StringBuilder for use in manifest generated TraceEventParsers + These have been moved to protected members of TraceEvent. The result is that in stead of writing + sb.XmlAttrib(...) you write XmlAttrib(sb, ...) + * References to Pdb in names have been replaced with 'Symbol' to conform to naming guidelines. + + *********************************************************************************************** +Version 1.0.0.4 - Initial stable release + + Mostly this was insuring that the library was cleaned up in preparation + for release the TraceParserGen tool + + Improved the docs, removed old code, fixed some naming convention stuff + + * Additional changes from the PreRelease copy to the first Stable release + + * The arguments to AddCallbackForProviderEvent were reversed!!!! (now provider than event) + * The arguments to Observe(string, string)!!!! (now provider than event) + * Event names for these APIs must include a / between the Task and Opcode names + + * Many Events in KernelTraceEventParser were harmonized to be consistent with other conventions + * Events of the form PageFault* were typically renamed to Memory* + * The 'End' suffix was renamed to 'Stop' (its official name) + * PerfInfoSampleProf -> PerfInfoSample + * PerfInfoSampleProf -> PerfInfoSample + * ReadyThread -> DispatcherReadyThread + * StackWalkTraceData -> StackWalkStackTraceData + * FileIo -> FileIO + * DiskIo -> DiskIO + + * Many Events in SymbolTraceEventParser were harmonized to be consistent with other conventions + * names with Symbol -> ImageID diff --git a/test/Qwiq.Benchmark/_TraceEventProgrammersGuide.docx b/test/Qwiq.Benchmark/_TraceEventProgrammersGuide.docx new file mode 100644 index 0000000000000000000000000000000000000000..360c82005d4df1d1bf8a1885dc9a869fbc0a0c27 GIT binary patch literal 196963 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 !^Ne3lGeYJQLpWfaRTg2o3O)f+hWqer=+9v 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 - /// Class IdentityFieldValue. + /// Represents an identity /// public class IdentityFieldValue { @@ -15,20 +15,19 @@ public class IdentityFieldValue private static readonly Regex AccountNameRegex = new Regex("^.+<(.+@.+)>$", RegexOptions.Compiled | RegexOptions.IgnoreCase); // "Chris Johnson" - private static readonly Regex DisplayNameRegex = new Regex( - @"^[^<\\]*(?:<[^>]*)?$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex DisplayNameRegex = + new Regex(@"^[^<\\]*(?:<[^>]*)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); // "Chris Johnson " private static readonly Regex DomainAccountRegex = new Regex(@"^.+<(.+\\.+)>$", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ScopeRegex = new Regex( - @"^\[[0-9A-Za-z ]+\]\\(.+)<([0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12})>$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + @"^\[[0-9A-Za-z ]+\]\\(.+)<([0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12})>$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex VsidRegex = new Regex( - @"^\[[0-9A-Za-z ]+\]\\(.+)$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + @"^\[[0-9A-Za-z ]+\]\\(.+)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// Initializes a new instance of the class. @@ -54,10 +53,8 @@ public IdentityFieldValue(string displayName, string fullName, string teamFounda { FullName = fullName; - if (!string.IsNullOrEmpty(teamFoundationId)) - { - if (Guid.TryParse(teamFoundationId, out Guid tfsid)) TeamFoundationId = teamFoundationId; - } + if (!string.IsNullOrEmpty(teamFoundationId) && Guid.TryParse(teamFoundationId, out Guid tfsid)) + TeamFoundationId = teamFoundationId; var arr = FullName.Split(IdentityConstants.DomainAccountNameSeparator); if (arr.Length != 2 || arr[1] == TeamFoundationId) return; @@ -119,10 +116,7 @@ public IdentityFieldValue(string displayName) DisplayPart = displayName; return; } - if (TryGetDisplayName(displayName, out str2)) - { - DisplayPart = str2; - } + if (TryGetDisplayName(displayName, out str2)) DisplayPart = str2; } } @@ -184,9 +178,21 @@ public string IdentityName } } - public string TeamFoundationId { get; } + public static explicit operator string(IdentityFieldValue value) + { + return value.IdentityName; + } + + /// + public override string ToString() + { + if (string.IsNullOrEmpty(IdentityName)) return DisplayName; + + return $"{DisplayName} <{AccountName}>".ToString(CultureInfo.InvariantCulture); + } + private static bool TryGetAccountName(string search, out string acccountName) { var match = AccountNameRegex.Match(search); @@ -250,21 +256,5 @@ private static bool TryGetVsid(string search, out Guid vsid, out string displayN displayName = string.Empty; return false; } - - /// - public override string ToString() - { - if (string.IsNullOrEmpty(IdentityName)) - { - return DisplayName; - } - - return ($"{DisplayName} <{AccountName}>").ToString(CultureInfo.InvariantCulture); - } - - public static explicit operator string(IdentityFieldValue value) - { - return value.IdentityName; - } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockIdentityManagementService.cs b/test/Qwiq.Mocks/MockIdentityManagementService.cs index 7347a546..9bbf4959 100644 --- a/test/Qwiq.Mocks/MockIdentityManagementService.cs +++ b/test/Qwiq.Mocks/MockIdentityManagementService.cs @@ -9,9 +9,6 @@ namespace Microsoft.Qwiq.Mocks { public class MockIdentityManagementService : IIdentityManagementService { - [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Danj instead.")] - public static readonly ITeamFoundationIdentity Danj =Identities.Danj; - [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Adamb instead.")] public static readonly ITeamFoundationIdentity Adamb = Identities.Adamb; @@ -27,12 +24,15 @@ public class MockIdentityManagementService : IIdentityManagementService [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Chrisjohns instead.")] public static readonly ITeamFoundationIdentity Chrisjohns = Identities.Chrisjohns; + [Obsolete("This field is depreciated and will be removed in a future version. Use Identities.Danj instead.")] + public static readonly ITeamFoundationIdentity Danj = Identities.Danj; private readonly IDictionary _accountNameMappings; + private readonly IDictionary _descriptorMappings; /// - /// Initializes a new instance of the IMS with Contoso users (danj, adamb, chrisj, chrisjoh, chrisjohn, chrisjohns) + /// Initializes a new instance of the IMS with Contoso users (danj, adamb, chrisj, chrisjoh, chrisjohn, chrisjohns) /// public MockIdentityManagementService() : this(Identities.All) @@ -44,13 +44,11 @@ public MockIdentityManagementService(IEnumerable identi { } - - /// - /// Creates a new instance of the IMS + /// Creates a new instance of the IMS /// /// - /// Collection of alias to . + /// Collection of alias to . /// public MockIdentityManagementService(IDictionary accountNameMappings) { @@ -76,50 +74,52 @@ public MockIdentityManagementService(IDictionary - /// Creates a new instance of the IMS + /// Creates a new instance of the IMS /// /// - /// Collection of alias and display names for which to initialize the IMS + /// Collection of alias and display names for which to initialize the IMS /// public MockIdentityManagementService(IDictionary userMappings) - : this(userMappings.ToDictionary(kvp => kvp.Key, kvp => new MockTeamFoundationIdentity(kvp.Value, kvp.Key + "@" + MockIdentityDescriptor.Domain) as ITeamFoundationIdentity)) + : this( + userMappings.ToDictionary( + kvp => kvp.Key, + kvp => new MockTeamFoundationIdentity( + kvp.Value, + kvp.Key + + "@" + + MockIdentityDescriptor + .Domain) as ITeamFoundationIdentity)) { } public MockIdentityManagementService(IDictionary> accountMappings) { _accountNameMappings = accountMappings?.ToDictionary(k => k.Key, e => e.Value.ToArray()) - ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + ?? new Dictionary(StringComparer.OrdinalIgnoreCase); _descriptorMappings = new Dictionary(IdentityDescriptorComparer.Default); foreach (var accounts in _accountNameMappings.Values) { - foreach (var account in accounts) - { - _descriptorMappings.Add(account.Descriptor, account); - } + foreach (var account in accounts) _descriptorMappings.Add(account.Descriptor, account); } } - public IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier) { return new IdentityDescriptor(identityType, identifier); } /// - /// Read identities for given descriptors. + /// Read identities for given descriptors. /// - /// Collection of + /// Collection of /// - /// An array of , corresponding 1 to 1 with input descriptor array. + /// An array of , corresponding 1 to 1 with input descriptor array. /// public IEnumerable ReadIdentities(IEnumerable descriptors) { foreach (var descriptor in descriptors) { - - var success = _descriptorMappings.TryGetValue(descriptor, out ITeamFoundationIdentity identity); Trace.TraceInformation($"{nameof(MockIdentityManagementService)}: Searching for {descriptor}; Success: {success}"); @@ -128,7 +128,9 @@ public IEnumerable ReadIdentities(IEnumerable>> ReadIdentities(IdentitySearchFactor searchFactor, IEnumerable searchFactorValues) + public IEnumerable>> ReadIdentities( + IdentitySearchFactor searchFactor, + IEnumerable searchFactorValues) { Trace.TraceInformation($"Searching for {searchFactor}: {string.Join(", ", searchFactorValues)}"); @@ -136,42 +138,17 @@ public IEnumerable>> R { // Alternate login username case IdentitySearchFactor.Alias: - foreach (var value in searchFactorValues) - { - if (_accountNameMappings.ContainsKey(value)) - { - yield return - new KeyValuePair>( - value, - _accountNameMappings[value]); - } - else - { - yield return new KeyValuePair>(value, new ITeamFoundationIdentity[0]); - } - } + foreach (var keyValuePair in SearchByAlias(searchFactorValues)) yield return keyValuePair; break; // Windows NT account name: domain\alias. case IdentitySearchFactor.AccountName: - foreach (var value in searchFactorValues) - { - yield return new KeyValuePair>( - value, - Locate(identity => identity.GetAttribute("Account", string.Empty).StartsWith(value, StringComparison.OrdinalIgnoreCase))); - } + foreach (var keyValuePair1 in SearchByAccountName(searchFactorValues)) yield return keyValuePair1; break; // Display name case IdentitySearchFactor.DisplayName: - foreach (var value in searchFactorValues) - { - yield return - new KeyValuePair>( - value, - Locate(identity => - value.Equals(identity.DisplayName, StringComparison.OrdinalIgnoreCase) || - identity.DisplayName.StartsWith(value, StringComparison.OrdinalIgnoreCase))); - } + foreach (var keyValuePair2 in SearchByDisplayName(searchFactorValues)) yield return keyValuePair2; break; + case IdentitySearchFactor.AdministratorsGroup: case IdentitySearchFactor.Identifier: case IdentitySearchFactor.MailAddress: @@ -180,6 +157,36 @@ public IEnumerable>> R } } + private IEnumerable>> SearchByDisplayName(IEnumerable searchFactors) + { + return searchFactors.Select(searchFactor => + { + bool Predicate(ITeamFoundationIdentity identity) => Comparer.OrdinalIgnoreCase.Equals(identity.DisplayName, searchFactor) || identity.DisplayName.StartsWith(searchFactor, StringComparison.OrdinalIgnoreCase); + + return new KeyValuePair>(searchFactor, Locate(Predicate)); + }); + } + + private IEnumerable>> SearchByAccountName(IEnumerable searchFactors) + { + return searchFactors.Select(searchFactor => + { + bool Predicate(ITeamFoundationIdentity identity) => identity.GetUserAccountName().StartsWith(searchFactor, StringComparison.OrdinalIgnoreCase); + + return new KeyValuePair>(searchFactor, Locate(Predicate)); + }); + } + + private IEnumerable>> SearchByAlias(IEnumerable searchFactors) + { + foreach (var searchFactor in searchFactors) + { + if (_accountNameMappings.ContainsKey(searchFactor)) + yield return new KeyValuePair>(searchFactor, _accountNameMappings[searchFactor]); + else yield return new KeyValuePair>(searchFactor, new ITeamFoundationIdentity[0]); + } + } + /// public ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue) { @@ -188,13 +195,10 @@ public ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, s private IEnumerable Locate(Func predicate) { - return _accountNameMappings - .Values - .SelectMany(a => a, (a, i) => new { a, i }) - .Where(t => predicate(t.i)) - .Select(t => t.i) - .ToArray(); + return _accountNameMappings.Values.SelectMany(a => a, (a, i) => new { a, i }) + .Where(t => predicate(t.i)) + .Select(t => t.i) + .ToArray(); } } -} - +} \ No newline at end of file From cda29bc6c7270b28e824defcf7515b5fe1444872 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 11:58:22 -0700 Subject: [PATCH 156/251] Update warning message When multiple identities are detected during conversion, a warning message is logged. This message now contains the values that were returned by the search and the value selected for mapping. --- src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs index 8e7225ca..6ac65696 100644 --- a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs +++ b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs @@ -49,7 +49,7 @@ private Dictionary GetIdentityNames(params string[] displayNames var retval = kvp.Value.FirstOrDefault(); if (kvp.Value.Length > 1) { - Trace.TraceWarning("Display Name '{0}' contains more than one alias; choosing '{1}'.", kvp.Key, retval); + Trace.TraceWarning("Multiple identities found matching '{0}':\r\n\r\n- {1}\r\n\r\nChoosing '{2}'.", kvp.Key, string.Join("\r\n- ", kvp.Value), retval); } return retval; }, From f58fa988bc18fdfa5e93471d4045956a49f8f168 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 11:58:54 -0700 Subject: [PATCH 157/251] Add documentation to IdentitySearchFactor --- src/Qwiq.Core/IdentitySearchFactor.cs | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core/IdentitySearchFactor.cs b/src/Qwiq.Core/IdentitySearchFactor.cs index accf635b..91996852 100644 --- a/src/Qwiq.Core/IdentitySearchFactor.cs +++ b/src/Qwiq.Core/IdentitySearchFactor.cs @@ -2,13 +2,52 @@ namespace Microsoft.Qwiq { public enum IdentitySearchFactor { + /// + /// NT account name (domain\alias or alias@domain.tld) + /// AccountName = 0, + + /// + /// Display name + /// DisplayName = 1, + + /// + /// Find project admin group + /// AdministratorsGroup = 2, + + /// + /// Find the identity using the identifier + /// + /// Identifier = 3, + + /// + /// Email address + /// MailAddress = 4, + + /// + /// A general search for identity + /// + /// + /// This is the default search factor for shorter overloads of , + /// and typically the correct choice for user input. Use the general search factor to find one or more identities by + /// one of the following properties: + /// - Display name + /// - Account name + /// - Unique name + /// Unique name may be easier to type than display name for users. It can also be used to indicate a single identity + /// when two or more identities share the same display name (e.g. "John Smith") + /// + /// + /// General = 5, + + /// + /// Alternate login name + /// Alias = 6 } -} - +} \ No newline at end of file From 7c9ee2eecc600da218e93c56239867eecf935270 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 15:27:01 -0700 Subject: [PATCH 158/251] Remove IdentityManagementServiceExtensions The extension methods for IIdentityManagementService have been removed from the Identity project. Respective logic has been encapsulated into the consuming identity converters. --- .../ITeamFoundationIdentity.Extensions.cs | 38 ++-- src/Qwiq.Core/ITeamFoundationIdentity.cs | 6 +- src/Qwiq.Core/IdentityFieldValue.cs | 51 +++--- src/Qwiq.Core/TeamFoundationIdentity.cs | 12 +- .../DisplayNameToAliasValueConverter.cs | 23 ++- .../IdentityAliasValueConverter.cs | 68 +++++++- .../IdentityManagementService.Extensions.cs | 115 ------------- src/Qwiq.Identity/Qwiq.Identity.csproj | 1 - .../Identity/IdentityFieldValueTests.cs | 12 +- ...entityManagementService.ExtensionsTests.cs | 162 ------------------ .../Qwiq.Identity.UnitTests.csproj | 1 - ...dentityManagementServiceExtensionsTests.cs | 52 ------ .../Qwiq.IntegrationTests.csproj | 1 - .../MockIdentityManagementService.cs | 17 +- test/Qwiq.Mocks/MockTeamFoundationIdentity.cs | 14 +- 15 files changed, 183 insertions(+), 390 deletions(-) delete mode 100644 src/Qwiq.Identity/IdentityManagementService.Extensions.cs delete mode 100644 test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs delete mode 100644 test/Qwiq.Integration.Tests/Identity/Soap/IdentityManagementServiceExtensionsTests.cs diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs index 6f752ae5..be223f58 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.Extensions.cs @@ -6,28 +6,46 @@ namespace Microsoft.Qwiq public static partial class Extensions // ReSharper restore InconsistentNaming { - - public static string GetUserAlias(this ITeamFoundationIdentity identity) + /// + /// Gets the identity name from the specified instance. + /// + /// An instance of . + /// + /// The value of the property of the identity if it is not null; + /// otherwise, . + /// + public static string GetIdentityName(this ITeamFoundationIdentity identity) { if (identity == null) return null; - return identity.GetAttribute(IdentityAttributeTags.Alias, null) - ?? new IdentityFieldValue(identity).Alias; + return identity.GetAttribute(IdentityAttributeTags.AccountName, null) ?? new IdentityFieldValue(identity).IdentityName; } + /// + /// Gets the user account name from the specified instance. + /// + /// An instance of . + /// + /// The value of the property; otherwise, + /// . + /// public static string GetUserAccountName(this ITeamFoundationIdentity identity) { if (identity == null) return null; - return identity.GetAttribute(IdentityAttributeTags.AccountName, null) - ?? new IdentityFieldValue(identity).AccountName; + return identity.GetAttribute(IdentityAttributeTags.AccountName, null) ?? new IdentityFieldValue(identity).AccountName; } - public static string GetIdentityName(this ITeamFoundationIdentity identity) + /// + /// Gets the user account (logon) name from the specified instance. + /// + /// An instance of . + /// + /// + /// + public static string GetUserAlias(this ITeamFoundationIdentity identity) { - if (identity == null) return null; - - return identity.GetAttribute(IdentityAttributeTags.AadUserPrincipalName, null) ?? new IdentityFieldValue(identity).IdentityName; + return identity == null ? null : new IdentityFieldValue(identity).LogonName; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.cs b/src/Qwiq.Core/ITeamFoundationIdentity.cs index f0762564..e3fe5e06 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.cs @@ -12,10 +12,12 @@ public interface ITeamFoundationIdentity IIdentityDescriptor Descriptor { get; } /// - /// Gets the full name of the identity for display purposes. The display name can come from the identity provider, or + /// Gets the full name of the identity for display purposes. The display name can come from the identity provider (e.g. Active Directory), or /// may have been set as a custom display name within TFS. /// - /// The display name. + /// + /// If the identity provider does not supply a full name, and no custom display name is set, another property like account name or email address will be used as the display name. + /// string DisplayName { get; } /// diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index b8abd5a0..ab830174 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -43,26 +43,26 @@ public IdentityFieldValue(ITeamFoundationIdentity identity) /// Initializes a new instance of the class. /// /// The display name (e.g. "Chris Johnson <chrisjohns@contoso.com>"). - /// + /// /// The value from the descriptor identifier (e.g. /// CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C\chrisjohns@contoso.com). /// /// The security identifier (SID) for the identity. - public IdentityFieldValue(string displayName, string fullName, string teamFoundationId) + public IdentityFieldValue(string displayName, string identifier, string teamFoundationId) : this(displayName) { - FullName = fullName; + Identifier = identifier; if (!string.IsNullOrEmpty(teamFoundationId) && Guid.TryParse(teamFoundationId, out Guid tfsid)) TeamFoundationId = teamFoundationId; - var arr = FullName.Split(IdentityConstants.DomainAccountNameSeparator); + var arr = Identifier.Split(IdentityConstants.DomainAccountNameSeparator); if (arr.Length != 2 || arr[1] == TeamFoundationId) return; if (arr[1].Contains("@")) { Email = arr[1]; - Alias = arr[1].Split('@')[0]; + LogonName = arr[1].Split('@')[0]; AccountName = arr[1]; if (Guid.TryParse(arr[0], out Guid guid)) Domain = arr[0]; } @@ -70,12 +70,12 @@ public IdentityFieldValue(string displayName, string fullName, string teamFounda { if (Guid.TryParse(arr[0], out Guid guid)) { - Alias = arr[1]; + LogonName = arr[1]; } else { Domain = arr[0]; - Alias = arr[1]; + LogonName = arr[1]; } } } @@ -93,16 +93,18 @@ public IdentityFieldValue(string displayName) } if (TryGetDomainAndAccountName(displayName, out string str2)) { + AccountName = str2; + + var strArray = str2.Split(IdentityConstants.DomainAccountNameSeparator); if (strArray.Length != 2) { - DisplayPart = displayName; return; } Domain = strArray[0]; - Alias = strArray[1]; - DisplayPart = displayName; + LogonName = strArray[1]; + return; } if (TryGetAccountName(displayName, out str2)) @@ -111,7 +113,7 @@ public IdentityFieldValue(string displayName) if (str2.Contains("@")) { Email = str2; - Alias = str2.Split('@')[0]; + LogonName = str2.Split('@')[0]; } DisplayPart = displayName; return; @@ -120,13 +122,17 @@ public IdentityFieldValue(string displayName) } } + /// + /// Gets the the User principal name (UPN) or the down-level login name. + /// + /// This can be in the UPN format (e.g. UserName@Example.Microsoft.com) or the down-level logon name format (e.g. EXAMPLE\UserName). public string AccountName { get; } /// - /// Gets the alias. + /// Gets the user account (logon) name. /// - /// The alias parsed from . - public string Alias { get; } + /// The logon name parsed from , User Principal Name, or down-level logon name. + public string LogonName { get; } /// /// Gets the display name. @@ -156,14 +162,15 @@ public IdentityFieldValue(string displayName) /// Gets the full name. /// /// The full name as determined by the descriptor identifier. - public string FullName { get; } + /// + public string Identifier { get; } /// /// Gets the name of the identity. /// /// - /// The if it exists, the qualified \ if it exists, - /// the if it exists, or empty. + /// The if it exists, the qualified \ if it exists, + /// the if it exists, or empty. /// public string IdentityName { @@ -171,8 +178,8 @@ public string IdentityName { if (!string.IsNullOrEmpty(Email)) return Email; if (!string.IsNullOrEmpty(Domain)) - return string.Format(CultureInfo.InvariantCulture, IdentityConstants.DomainQualifiedAccountNameFormat, Domain, Alias); - if (!string.IsNullOrEmpty(Alias)) return Alias; + return string.Format(CultureInfo.InvariantCulture, IdentityConstants.DomainQualifiedAccountNameFormat, Domain, LogonName); + if (!string.IsNullOrEmpty(LogonName)) return LogonName; return null; } @@ -188,9 +195,9 @@ public static explicit operator string(IdentityFieldValue value) /// public override string ToString() { - if (string.IsNullOrEmpty(IdentityName)) return DisplayName; - - return $"{DisplayName} <{AccountName}>".ToString(CultureInfo.InvariantCulture); + return string.IsNullOrEmpty(IdentityName) + ? DisplayName + : $"{DisplayName} <{AccountName}>".ToString(CultureInfo.InvariantCulture); } private static bool TryGetAccountName(string search, out string acccountName) diff --git a/src/Qwiq.Core/TeamFoundationIdentity.cs b/src/Qwiq.Core/TeamFoundationIdentity.cs index d182c65d..b61da1fe 100644 --- a/src/Qwiq.Core/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core/TeamFoundationIdentity.cs @@ -7,12 +7,20 @@ namespace Microsoft.Qwiq public abstract class TeamFoundationIdentity : ITeamFoundationIdentity, IEquatable { private string _uniqueName; + protected static readonly IIdentityDescriptor[] ZeroLengthArrayOfIdentityDescriptor = new IIdentityDescriptor[0]; + private TeamFoundationIdentity() + { + MemberOf = ZeroLengthArrayOfIdentityDescriptor; + Members = ZeroLengthArrayOfIdentityDescriptor; + TeamFoundationId = Guid.Empty; + } protected internal TeamFoundationIdentity( bool isActive, Guid teamFoundationId, int uniqueUserId ) + : this() { IsActive = isActive; TeamFoundationId = teamFoundationId; @@ -47,9 +55,9 @@ public virtual bool IsContainer } } - public abstract IEnumerable MemberOf { get; } + public virtual IEnumerable MemberOf { get; } - public abstract IEnumerable Members { get; } + public virtual IEnumerable Members { get; } public virtual Guid TeamFoundationId { get; } diff --git a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs index 6ac65696..c1a63a21 100644 --- a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs +++ b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using System.Linq; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Identity { /// @@ -39,8 +41,8 @@ public object Map(object value) private Dictionary GetIdentityNames(params string[] displayNames) { - return _identityManagementService - .GetAliasesForDisplayNames(displayNames) + return + GetAliasesForDisplayNames(displayNames) .ToDictionary( kvp => kvp.Key, kvp => @@ -55,5 +57,22 @@ private Dictionary GetIdentityNames(params string[] displayNames }, Comparer.OrdinalIgnoreCase); } + + private IDictionary GetAliasesForDisplayNames(string[] displayNames) + { + if (displayNames == null) throw new ArgumentNullException(nameof(displayNames)); + + return _identityManagementService.ReadIdentities(IdentitySearchFactor.DisplayName, displayNames) + .ToDictionary( + kvp => kvp.Key, + kvp => kvp + .Value.Where( + identity => identity != null + && !identity.IsContainer + && identity.UniqueUserId == IdentityConstants.ActiveUniqueId) + .Select(i => i.GetUserAlias()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray()); + } } } diff --git a/src/Qwiq.Identity/IdentityAliasValueConverter.cs b/src/Qwiq.Identity/IdentityAliasValueConverter.cs index f2afd0be..0370bfbe 100644 --- a/src/Qwiq.Identity/IdentityAliasValueConverter.cs +++ b/src/Qwiq.Identity/IdentityAliasValueConverter.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Identity { @@ -58,8 +61,71 @@ public object Map(object value) private string[] GetIdentityNames(params string[] aliases) { - var identities = _identityManagementService.GetIdentityForAliases(aliases.ToList(), _tenantId, _domains); + var identities = GetIdentityForAliases(aliases.ToList(), _tenantId, _domains); return identities.Select(i => i.Value.GetIdentityName() ?? i.Key).ToArray(); } + + private IDictionary GetIdentityForAliases( + ICollection logonNames, + string tenantId, + params string[] domains) + { + if (string.IsNullOrWhiteSpace(tenantId)) throw new ArgumentNullException(nameof(tenantId)); + if (domains == null) throw new ArgumentNullException(nameof(domains)); + if (logonNames == null) throw new ArgumentNullException(nameof(logonNames)); + if (!domains.Any()) throw new ArgumentException(nameof(domains)); + if (!logonNames.Any()) throw new ArgumentException(nameof(logonNames)); + + var descriptorsToAliasLookup = CreatePossibleIdentityDescriptors(logonNames, domains, tenantId); + var identities = GetIdentitiesForAliases(descriptorsToAliasLookup); + var aliasesWithMissingIdentities = logonNames.Except(identities.Keys, Comparer.OrdinalIgnoreCase); + foreach (var mil in aliasesWithMissingIdentities) + { + identities.Add(mil, null); + } + return identities; + } + + private IDictionary> CreatePossibleIdentityDescriptors( + IEnumerable aliases, + string[] domains, + string tenantId) + { + var descriptors = new Dictionary>(); + foreach (var alias in aliases) + { + var descriptorsForAlias = new List(); + foreach (var domain in domains) + { + var loggedInAccountString = $"{tenantId}\\{alias}@{domain}".ToString(CultureInfo.InvariantCulture); + + descriptorsForAlias.Add(_identityManagementService.CreateIdentityDescriptor(IdentityConstants.ClaimsType, loggedInAccountString)); + descriptorsForAlias.Add(_identityManagementService.CreateIdentityDescriptor(IdentityConstants.BindPendingIdentityType,IdentityConstants.BindPendingSidPrefix + loggedInAccountString)); + } + + descriptors.Add(alias, descriptorsForAlias); + } + + return descriptors; + } + + private IDictionary GetIdentitiesForAliases( + IDictionary> aliasDescriptors) + { + var descriptors = aliasDescriptors.SelectMany(ad => ad.Value).ToList(); + var descriptorToAliasLookup = aliasDescriptors + .SelectMany(ad => ad.Value.Select(d => new KeyValuePair(d.ToString(), ad.Key))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase); + var validIdentities = new Dictionary(StringComparer.OrdinalIgnoreCase); + var lookupResults = _identityManagementService.ReadIdentities(descriptors); + foreach (var identity in lookupResults.Where(id => id != null)) + { + var lookupKey = identity.Descriptor.ToString(); + var alias = descriptorToAliasLookup[lookupKey]; + if (!validIdentities.ContainsKey(alias)) validIdentities.Add(alias, identity); + } + + return validIdentities; + } } } \ No newline at end of file diff --git a/src/Qwiq.Identity/IdentityManagementService.Extensions.cs b/src/Qwiq.Identity/IdentityManagementService.Extensions.cs deleted file mode 100644 index 6cada985..00000000 --- a/src/Qwiq.Identity/IdentityManagementService.Extensions.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Microsoft.VisualStudio.Services.Common; - -namespace Microsoft.Qwiq.Identity -{ - public static class IdentityManagementServiceExtensions - { - private const string TfsLoggedInIdentityType = "Microsoft.IdentityModel.Claims.ClaimsIdentity"; - - public static string[] GetAliasesForDisplayName(this IIdentityManagementService ims, string displayName) - { - return GetAliasesForDisplayNames(ims, new[] {displayName})[displayName]; - } - - public static IDictionary GetAliasesForDisplayNames(this IIdentityManagementService ims, string[] displayNames) - { - if (ims == null) throw new ArgumentNullException(nameof(ims)); - if (displayNames == null) throw new ArgumentNullException(nameof(displayNames)); - - return ims.ReadIdentities(IdentitySearchFactor.DisplayName, displayNames) - .ToDictionary( - kvp => kvp.Key, - kvp => - kvp.Value - .Where(identity => identity != null && !identity.IsContainer && identity.UniqueUserId == IdentityConstants.ActiveUniqueId) - .Select(i => i.GetUserAlias()) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray()); - } - - public static ITeamFoundationIdentity GetIdentityForAlias(this IIdentityManagementService ims, string alias, - string tenantId, params string[] domains) - { - return GetIdentityForAliases(ims, new[] {alias}, tenantId, domains)[alias]; - } - - public static IDictionary GetIdentityForAliases( - this IIdentityManagementService ims, ICollection aliases, string tenantId, params string[] domains) - { - if (ims == null) throw new ArgumentNullException(nameof(ims)); - if (string.IsNullOrWhiteSpace(tenantId)) throw new ArgumentNullException(nameof(tenantId)); - if (domains == null) throw new ArgumentNullException(nameof(domains)); - if (aliases == null) throw new ArgumentNullException(nameof(aliases)); - if (!domains.Any()) throw new ArgumentException(nameof(domains)); - if (!aliases.Any()) throw new ArgumentException(nameof(aliases)); - - var descriptorsToAliasLookup = CreatePossibleIdentityDescriptors(ims, aliases, domains, tenantId); - var identities = GetIdentitiesForAliases(ims, descriptorsToAliasLookup); - - var aliasesWithMissingIdentities = aliases.Except(identities.Keys); - var searchedIdentities = SearchForIdentitiesForAliases(ims, aliasesWithMissingIdentities); - - return identities.Union(searchedIdentities).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } - - private static IEnumerable> SearchForIdentitiesForAliases(IIdentityManagementService ims, IEnumerable aliases) - { - var missingIdentitiesList = ims.ReadIdentities(IdentitySearchFactor.AccountName, aliases.ToList()); - return missingIdentitiesList.Select(mil => new KeyValuePair(mil.Key, mil.Value.FirstOrDefault())); - } - - private static IDictionary GetIdentitiesForAliases(IIdentityManagementService ims, IDictionary> aliasDescriptors) - { - var descriptors = aliasDescriptors.SelectMany(ad => ad.Value).ToList(); - var descriptorToAliasLookup = - aliasDescriptors - .SelectMany( - ad => - ad.Value.Select(d => new KeyValuePair(BuildDescriptorLookupKey(d), ad.Key))) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase); - var validIdentities = new Dictionary(StringComparer.OrdinalIgnoreCase); - var lookupResults = ims.ReadIdentities(descriptors); - foreach (var identity in lookupResults.Where(id => id != null)) - { - var lookupKey = BuildDescriptorLookupKey(identity.Descriptor); - var alias = descriptorToAliasLookup[lookupKey]; - if (!validIdentities.ContainsKey(alias)) - { - validIdentities.Add(alias, identity); - } - } - - return validIdentities; - } - - private static IDictionary> CreatePossibleIdentityDescriptors(IIdentityManagementService ims, ICollection aliases, ICollection domains, string tenantId) - { - var descriptors = new Dictionary>(); - foreach (var alias in aliases) - { - var descriptorsForAlias = new List(); - foreach (var domain in domains) - { - var loggedInAccountString = $"{tenantId}\\{alias}@{domain}"; - - descriptorsForAlias.Add(ims.CreateIdentityDescriptor(IdentityConstants.ClaimsType, loggedInAccountString)); - descriptorsForAlias.Add(ims.CreateIdentityDescriptor(IdentityConstants.BindPendingIdentityType, - IdentityConstants.BindPendingSidPrefix + loggedInAccountString)); - } - - descriptors.Add(alias, descriptorsForAlias); - } - - return descriptors; - } - private static string BuildDescriptorLookupKey(IIdentityDescriptor descriptor) - { - return $"{descriptor.IdentityType}-{descriptor.Identifier}}}"; - } - } -} - diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 0f933a88..088474e0 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -23,7 +23,6 @@ - diff --git a/test/Qwiq.Core.Tests/Identity/IdentityFieldValueTests.cs b/test/Qwiq.Core.Tests/Identity/IdentityFieldValueTests.cs index c8a26ac3..441813b1 100644 --- a/test/Qwiq.Core.Tests/Identity/IdentityFieldValueTests.cs +++ b/test/Qwiq.Core.Tests/Identity/IdentityFieldValueTests.cs @@ -37,7 +37,7 @@ public void TeamFoundationId_is_null() [TestMethod] public void Alias_is_null() { - Result.Alias.ShouldBeNull(); + Result.LogonName.ShouldBeNull(); } [TestMethod] @@ -67,7 +67,7 @@ public void Email_is_null() [TestMethod] public void FullName_is_null() { - Result.FullName.ShouldBeNull(); + Result.Identifier.ShouldBeNull(); } [TestMethod] @@ -101,7 +101,7 @@ public void TeamFoundationId_is_null() [TestMethod] public void Alias_is_null() { - Result.Alias.ShouldEqual("chrisjohns"); + Result.LogonName.ShouldEqual("chrisjohns"); } [TestMethod] @@ -131,7 +131,7 @@ public void Email_is_null() [TestMethod] public void FullName_is_null() { - Result.FullName.ShouldBeNull(); + Result.Identifier.ShouldBeNull(); } [TestMethod] @@ -166,7 +166,7 @@ public void TeamFoundationId_is_null() [TestMethod] public void Alias_is_null() { - Result.Alias.ShouldEqual("chrisjohns"); + Result.LogonName.ShouldEqual("chrisjohns"); } [TestMethod] @@ -196,7 +196,7 @@ public void Email_is_null() [TestMethod] public void FullName_is_null() { - Result.FullName.ShouldEqual("CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C\\chrisjohns@contoso.com"); + Result.Identifier.ShouldEqual("CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C\\chrisjohns@contoso.com"); } [TestMethod] diff --git a/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs b/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs deleted file mode 100644 index 57fb6c3e..00000000 --- a/test/Qwiq.Identity.Tests/IdentityManagementService.ExtensionsTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -using Microsoft.Qwiq.Mocks; -using Microsoft.Qwiq.Tests.Common; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Should; - -namespace Microsoft.Qwiq.Identity -{ - public abstract class IdentityManagementServiceExtensionsTests : ContextSpecification - { - protected const string KnownSearchAliasForChrisjoh = "chrisjo"; - protected const string KnownSearchAliasForDanj = "dan"; - protected const string UnknownAliasA = "none"; - protected const string UnknownAliasB = "unknown"; - - protected IIdentityManagementService IdentityService { get; set; } - - public override void Given() - { - IdentityService = new MockIdentityManagementService(); - base.Given(); - } - } - - public abstract class SingularIdentityManagementServiceExtensionsTests : IdentityManagementServiceExtensionsTests - { - protected string Alias { get; set; } - protected ITeamFoundationIdentity ExpectedIdentity { get; set; } - protected ITeamFoundationIdentity ActualIdentity { get; set; } - - public override void When() - { - ActualIdentity = IdentityService.GetIdentityForAlias(Alias, MockIdentityDescriptor.TenantId, MockIdentityDescriptor.Domain, "otherdomain"); - } - - [TestMethod] - public void the_actual_identity_is_equal_to_the_expected() - { - ActualIdentity.ShouldEqual(ExpectedIdentity); - } - - } - - [TestClass] - public class Given_an_alias_which_can_be_resolved_by_identity_descriptor : SingularIdentityManagementServiceExtensionsTests - { - public override void Given() - { - ExpectedIdentity = Identities.Chrisj; - Alias = ExpectedIdentity.GetUserAlias(); - base.Given(); - } - } - - [TestClass] - public class Given_an_alias_which_can_be_resolved_by_searching : SingularIdentityManagementServiceExtensionsTests - { - public override void Given() - { - ExpectedIdentity = Identities.Chrisjoh; - Alias = KnownSearchAliasForChrisjoh; - base.Given(); - } - } - - [TestClass] - public class Given_an_alias_which_cant_be_resolved : SingularIdentityManagementServiceExtensionsTests - { - public override void Given() - { - ExpectedIdentity = null; - Alias = UnknownAliasA; - base.Given(); - } - } - - public abstract class MultiIdentityManagementServiceExtensionsTests : IdentityManagementServiceExtensionsTests - { - protected string[] Aliases { get; set; } - protected IDictionary ActualIdentities { get; set; } - protected IDictionary ExpectedIdentities { get; set; } - - public override void When() - { - ActualIdentities = IdentityService.GetIdentityForAliases(Aliases, MockIdentityDescriptor.TenantId, MockIdentityDescriptor.Domain, "otherdomain"); - } - - [TestMethod] - public void the_actual_identities_are_equal_to_the_expected() - { - ActualIdentities.ShouldContainOnly(ExpectedIdentities); - } - } - - [TestClass] - public class Given_aliases_which_can_be_resolved_by_identity_descriptor : MultiIdentityManagementServiceExtensionsTests - { - public override void Given() - { - var contestant1 = Identities.Chrisj; - var contestant2 = Identities.Danj; - ExpectedIdentities = new Dictionary - { - { contestant1.GetUserAlias(), contestant1 }, - { contestant2.GetUserAlias(), contestant2 } - }; - Aliases = ExpectedIdentities.Keys.ToArray(); - base.Given(); - } - } - - [TestClass] - public class Given_aliases_which_can_be_resolved_by_searching : MultiIdentityManagementServiceExtensionsTests - { - public override void Given() - { - ExpectedIdentities = new Dictionary - { - { KnownSearchAliasForChrisjoh, Identities.Chrisjoh }, - { KnownSearchAliasForDanj, Identities.Danj } - }; - Aliases = ExpectedIdentities.Keys.ToArray(); - base.Given(); - } - } - - [TestClass] - public class Given_aliases_which_cant_be_resolved : MultiIdentityManagementServiceExtensionsTests - { - public override void Given() - { - ExpectedIdentities = new Dictionary - { - { UnknownAliasA, null }, - { UnknownAliasB, null } - }; - Aliases = ExpectedIdentities.Keys.ToArray(); - base.Given(); - } - } - - [TestClass] - public class Given_aliases_which_can_be_resolved_by_different_methods : MultiIdentityManagementServiceExtensionsTests - { - public override void Given() - { - var danj = Identities.Chrisj; - ExpectedIdentities = new Dictionary - { - { danj.GetUserAlias(), danj }, - { KnownSearchAliasForChrisjoh, Identities.Chrisjoh }, - { UnknownAliasB, null } - }; - Aliases = ExpectedIdentities.Keys.ToArray(); - base.Given(); - } - } -} - diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj index 1fdc6ef1..c5b60491 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj @@ -185,7 +185,6 @@ Properties\AssemblyInfo.Common.cs - diff --git a/test/Qwiq.Integration.Tests/Identity/Soap/IdentityManagementServiceExtensionsTests.cs b/test/Qwiq.Integration.Tests/Identity/Soap/IdentityManagementServiceExtensionsTests.cs deleted file mode 100644 index 53a05357..00000000 --- a/test/Qwiq.Integration.Tests/Identity/Soap/IdentityManagementServiceExtensionsTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Should; - -namespace Microsoft.Qwiq.Identity.Soap -{ - [TestClass] - public class Given_a_valid_user_display_name : SoapIdentityManagementServiceContextSpecification - { - private IEnumerable _results; - - public override void When() - { - _results = TimedAction(() => Instance.ReadIdentities(IdentitySearchFactor.DisplayName, new[]{ "PETER LAVALLEE" }).First().Value, "SOAP", "AliasForDisplayName"); - - Debug.Print("Results: " + _results.EachToUsefulString()); - } - - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - public void result_contains_a_single_alias_PELAVALL() - { - _results.Single().GetUserAlias().ShouldEqual("pelavall"); - } - } - - [TestClass] - public class Given_a_valid_user_alias : SoapIdentityManagementServiceContextSpecification - { - private ITeamFoundationIdentity _results; - - public override void When() - { - _results = TimedAction(() => Instance.GetIdentityForAlias("pelavall", "72F988BF-86F1-41AF-91AB-2D7CD011DB47", "microsoft.com"), "SOAP", "AliasForDisplayName"); - - Debug.Print("Results: " + _results.ToUsefulString()); - } - - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - public void result_contains_a_single_alias_PELAVALL() - { - _results.GetUserAlias().ShouldEqual("pelavall"); - } - } -} diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index fa4cae52..7a263cae 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -190,7 +190,6 @@ - diff --git a/test/Qwiq.Mocks/MockIdentityManagementService.cs b/test/Qwiq.Mocks/MockIdentityManagementService.cs index 9bbf4959..5b78fd8f 100644 --- a/test/Qwiq.Mocks/MockIdentityManagementService.cs +++ b/test/Qwiq.Mocks/MockIdentityManagementService.cs @@ -40,7 +40,7 @@ public MockIdentityManagementService() } public MockIdentityManagementService(IEnumerable identities) - : this(identities.ToDictionary(k => new IdentityFieldValue(k).Alias, e => e, StringComparer.OrdinalIgnoreCase)) + : this(identities.ToDictionary(k => new IdentityFieldValue(k).LogonName, e => e, StringComparer.OrdinalIgnoreCase)) { } @@ -140,7 +140,7 @@ public IEnumerable>> R case IdentitySearchFactor.Alias: foreach (var keyValuePair in SearchByAlias(searchFactorValues)) yield return keyValuePair; break; - // Windows NT account name: domain\alias. + // Windows NT account name: domain\alias or user@domain.tld case IdentitySearchFactor.AccountName: foreach (var keyValuePair1 in SearchByAccountName(searchFactorValues)) yield return keyValuePair1; break; @@ -159,26 +159,31 @@ public IEnumerable>> R private IEnumerable>> SearchByDisplayName(IEnumerable searchFactors) { + // TFS Matches, ignoring case, the property "Display Name" on the identity + return searchFactors.Select(searchFactor => { - bool Predicate(ITeamFoundationIdentity identity) => Comparer.OrdinalIgnoreCase.Equals(identity.DisplayName, searchFactor) || identity.DisplayName.StartsWith(searchFactor, StringComparison.OrdinalIgnoreCase); - + bool Predicate(ITeamFoundationIdentity identity) => Comparer.OrdinalIgnoreCase.Equals(identity.DisplayName, searchFactor); return new KeyValuePair>(searchFactor, Locate(Predicate)); }); } private IEnumerable>> SearchByAccountName(IEnumerable searchFactors) { + // TFS Matches, ignoring case, the property "Account", which is in the property bag of the identity + return searchFactors.Select(searchFactor => { - bool Predicate(ITeamFoundationIdentity identity) => identity.GetUserAccountName().StartsWith(searchFactor, StringComparison.OrdinalIgnoreCase); - + bool Predicate(ITeamFoundationIdentity identity) => Comparer.OrdinalIgnoreCase.Equals(identity.GetUserAccountName(), searchFactor); return new KeyValuePair>(searchFactor, Locate(Predicate)); }); } private IEnumerable>> SearchByAlias(IEnumerable searchFactors) { + // TFS Matches, ignoring case, the property "Alias", which is in the property bag of the identity + // NOTE: This may not be populated for all identities + foreach (var searchFactor in searchFactors) { if (_accountNameMappings.ContainsKey(searchFactor)) diff --git a/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs b/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs index 8303e423..341ef82c 100644 --- a/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs +++ b/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs @@ -20,20 +20,20 @@ public MockTeamFoundationIdentity( : base(isActive, teamFoundationId == Guid.Empty ? Guid.NewGuid() : teamFoundationId, isActive ? 0 : 1) { - DisplayName = displayName; - MemberOf = memberOf ?? Enumerable.Empty(); - Members = members ?? Enumerable.Empty(); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); + MemberOf = memberOf ?? ZeroLengthArrayOfIdentityDescriptor; + Members = members ?? ZeroLengthArrayOfIdentityDescriptor; IsContainer = false; - Descriptor = descriptor; + Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor)); - var f = new IdentityFieldValue(DisplayName, Descriptor.Identifier, null); + var f = new IdentityFieldValue(DisplayName, Descriptor.Identifier, TeamFoundationId.ToString()); _properties = new Dictionary(StringComparer.OrdinalIgnoreCase) { { IdentityAttributeTags.SchemaClassName, "User"}, { IdentityAttributeTags.Description, string.Empty }, { IdentityAttributeTags.Domain, f.Domain }, - { IdentityAttributeTags.AccountName, f.Alias }, + { IdentityAttributeTags.AccountName, f.AccountName }, { IdentityAttributeTags.DistinguishedName, string.Empty}, { IdentityAttributeTags.MailAddress, f.Email }, { IdentityAttributeTags.SpecialType, "Generic" }, @@ -58,7 +58,7 @@ public MockTeamFoundationIdentity(string displayName, string uniqueName) public override string GetAttribute(string name, string defaultValue) { - return _properties != null && _properties.TryGetValue(name, out object obj) + return _properties.TryGetValue(name, out object obj) ? obj?.ToString() ?? defaultValue : defaultValue; } From 8f4d46ebd3c6f8295fec7fd8f96c4a837c5e0ec3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 16:30:00 -0700 Subject: [PATCH 159/251] DRY out TfsConnectionFactory --- src/Qwiq.Core.Rest/TfsConnectionFactory.cs | 36 +++---------------- src/Qwiq.Core.Soap/TfsConnectionFactory.cs | 36 +++---------------- src/Qwiq.Core/AccessDeniedException.cs | 25 ++++++++++++++ src/Qwiq.Core/ITfsConnectionFactory.cs | 9 +++++ src/Qwiq.Core/Qwiq.Core.csproj | 2 ++ src/Qwiq.Core/TfsConnectionFactory.cs | 40 +++++++++++++++++----- 6 files changed, 76 insertions(+), 72 deletions(-) create mode 100644 src/Qwiq.Core/AccessDeniedException.cs create mode 100644 src/Qwiq.Core/ITfsConnectionFactory.cs diff --git a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs index f3ff5a4b..2eb5dfb8 100644 --- a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs +++ b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs @@ -1,13 +1,11 @@ using System; -using Microsoft.Qwiq.Credentials; -using Microsoft.TeamFoundation.Build.WebApi; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; namespace Microsoft.Qwiq.Client.Rest { - public class TfsConnectionFactory : Qwiq.TfsConnectionFactory + public class TfsConnectionFactory : TfsConnectionFactory { public static readonly ITfsConnectionFactory Default = Nested.Instance; @@ -15,39 +13,12 @@ private TfsConnectionFactory() { } - /// - public override ITeamProjectCollection Create(AuthenticationOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - var credentials = options.Credentials; - - foreach (var credential in credentials) - { - try - { - var tfsNative = ConnectToTfsCollection(options.Uri, credential); - var tfsProxy = tfsNative.AsProxy(); - options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfsProxy)); - - return tfsProxy; - } - catch (Exception e) - { - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); - } - } - - var nocreds = new AccessDeniedException("Invalid credentials"); - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); - throw nocreds; - } - - private static VssConnection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) + protected override ITeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) { var tfsServer = new VssConnection(endpoint, credentials); tfsServer.ConnectAsync(VssConnectMode.Automatic).GetAwaiter().GetResult(); if (!tfsServer.HasAuthenticated) throw new InvalidOperationException("Could not connect."); - return tfsServer; + return tfsServer.AsProxy(); } // ReSharper disable ClassNeverInstantiated.Local @@ -56,6 +27,7 @@ private class Nested { // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly TfsConnectionFactory Instance = new TfsConnectionFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler diff --git a/src/Qwiq.Core.Soap/TfsConnectionFactory.cs b/src/Qwiq.Core.Soap/TfsConnectionFactory.cs index 188b9de0..326bec5a 100644 --- a/src/Qwiq.Core.Soap/TfsConnectionFactory.cs +++ b/src/Qwiq.Core.Soap/TfsConnectionFactory.cs @@ -1,12 +1,10 @@ using System; -using Microsoft.Qwiq.Credentials; -using Microsoft.TeamFoundation.Build.Client; using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Client.Soap { - public class TfsConnectionFactory : Qwiq.TfsConnectionFactory + public class TfsConnectionFactory : TfsConnectionFactory { public static readonly ITfsConnectionFactory Default = Nested.Instance; @@ -15,38 +13,11 @@ private TfsConnectionFactory() } /// - public override ITeamProjectCollection Create(AuthenticationOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - var credentials = options.Credentials; - - foreach (var credential in credentials) - { - try - { - var tfsNative = ConnectToTfsCollection(options.Uri, credential); - var tfsProxy = tfsNative.AsProxy(); - - options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfsProxy)); - - return tfsProxy; - } - catch (Exception e) - { - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); - } - } - - var nocreds = new AccessDeniedException("Invalid credentials"); - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); - throw nocreds; - } - - private static TeamFoundation.Client.TfsTeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) + protected override ITeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) { var tfsServer = new TeamFoundation.Client.TfsTeamProjectCollection(endpoint, credentials); tfsServer.EnsureAuthenticated(); - return tfsServer; + return tfsServer.AsProxy(); } // ReSharper disable ClassNeverInstantiated.Local @@ -55,6 +26,7 @@ private class Nested { // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly TfsConnectionFactory Instance = new TfsConnectionFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler diff --git a/src/Qwiq.Core/AccessDeniedException.cs b/src/Qwiq.Core/AccessDeniedException.cs new file mode 100644 index 00000000..c2dd5f73 --- /dev/null +++ b/src/Qwiq.Core/AccessDeniedException.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.Serialization; + +namespace Microsoft.Qwiq +{ + [Serializable] + public class AccessDeniedException : Exception + { + public AccessDeniedException() + { + } + + public AccessDeniedException(string message) : base(message) + { + } + + public AccessDeniedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected AccessDeniedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Qwiq.Core/ITfsConnectionFactory.cs b/src/Qwiq.Core/ITfsConnectionFactory.cs new file mode 100644 index 00000000..55733cc6 --- /dev/null +++ b/src/Qwiq.Core/ITfsConnectionFactory.cs @@ -0,0 +1,9 @@ +using Microsoft.Qwiq.Credentials; + +namespace Microsoft.Qwiq +{ + public interface ITfsConnectionFactory + { + ITeamProjectCollection Create(AuthenticationOptions options); + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index da3444fc..84ffbea4 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -64,6 +64,7 @@ Properties\AssemblyInfo.Common.cs + @@ -143,6 +144,7 @@ + diff --git a/src/Qwiq.Core/TfsConnectionFactory.cs b/src/Qwiq.Core/TfsConnectionFactory.cs index 40b46cae..42f5bbfc 100644 --- a/src/Qwiq.Core/TfsConnectionFactory.cs +++ b/src/Qwiq.Core/TfsConnectionFactory.cs @@ -1,14 +1,38 @@ -using Microsoft.Qwiq.Credentials; +using System; + +using Microsoft.Qwiq.Credentials; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq { - public interface ITfsConnectionFactory - { - ITeamProjectCollection Create(AuthenticationOptions options); - } + public abstract class TfsConnectionFactory : ITfsConnectionFactory + where TConnection : ITeamProjectCollection - public abstract class TfsConnectionFactory : ITfsConnectionFactory { - public abstract ITeamProjectCollection Create(AuthenticationOptions options); + public virtual ITeamProjectCollection Create(AuthenticationOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + var credentials = options.Credentials; + + foreach (var credential in credentials) + { + try + { + var tfs = ConnectToTfsCollection(options.Uri, credential); + options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfs)); + return tfs; + } + catch (Exception e) + { + options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); + } + } + + var nocreds = new AccessDeniedException("Invalid credentials"); + options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); + throw nocreds; + } + + protected abstract TConnection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials); } -} +} \ No newline at end of file From afd0a2f98a8c06d91c16eed44e5cff4b60f6dc34 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 16:36:43 -0700 Subject: [PATCH 160/251] Encapsulate fields in base class --- .../TypeParser/TypeParserTests.cs | 42 +++++++++---------- .../TypeParser/TypeParserTestsContext.cs | 8 ++-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs b/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs index 7b8cc95b..5c263a36 100644 --- a/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs +++ b/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs @@ -13,7 +13,7 @@ public class when_parsing_a_value_type_to_nullable_value_type : TypeParserTestsC public override void When() { Expected = (int?)1; - Actual = TypeParser.Parse(1L); + Actual = Parser.Parse(1L); } [TestMethod] @@ -30,7 +30,7 @@ public class when_parsing_an_enum_value : TypeParserTestsContext public override void When() { Expected = Formatting.Indented; - Actual = TypeParser.Parse("Indented", Formatting.None); + Actual = Parser.Parse("Indented", Formatting.None); } [TestMethod] @@ -47,7 +47,7 @@ public class when_parsing_a_null_to_nonnullable_double : TypeParserTestsContext public override void When() { Expected = 11d; - Actual = TypeParser.Parse(null, 11); + Actual = Parser.Parse(null, 11); } [TestMethod] @@ -64,7 +64,7 @@ public class when_parsing_an_invalid_string_to_nullable_double : TypeParserTests public override void When() { Expected = null; - Actual = TypeParser.Parse(typeof(double?), (object)"blarg"); + Actual = Parser.Parse(typeof(double?), (object)"blarg"); } [TestMethod] @@ -81,7 +81,7 @@ public class when_parsing_an_invalid_string_to_nonnullable_double : TypeParserTe public override void When() { Expected = 0d; - Actual = TypeParser.Parse(typeof(double), (object)"blarg"); + Actual = Parser.Parse(typeof(double), (object)"blarg"); } [TestMethod] @@ -98,7 +98,7 @@ public class when_parsing_an_int64_string_to_int32 : TypeParserTestsContext public override void When() { Expected = 0; - Actual = TypeParser.Parse(typeof(int), (object)"0x7FFFFFFFFFFFFFFF"); + Actual = Parser.Parse(typeof(int), (object)"0x7FFFFFFFFFFFFFFF"); } [TestMethod] @@ -115,7 +115,7 @@ public class when_parsing_an_null_to_nonnullable_double : TypeParserTestsContext public override void When() { Expected = 0d; - Actual = TypeParser.Parse(typeof(double), null); + Actual = Parser.Parse(typeof(double), null); } [TestMethod] @@ -132,7 +132,7 @@ public class when_parsing_a_null_DateTime_to_nonnullable_DateTime : TypeParserTe public override void When() { Expected = new DateTime(2014, 1, 1); - Actual = TypeParser.Parse(typeof(DateTime), null, Expected); + Actual = Parser.Parse(typeof(DateTime), null, Expected); } [TestMethod] @@ -149,7 +149,7 @@ public class when_parsing_an_empty_string_nonnullable_double : TypeParserTestsCo public override void When() { Expected = 11d; - Actual = TypeParser.Parse("", 11); + Actual = Parser.Parse("", 11); } [TestMethod] @@ -166,7 +166,7 @@ public class when_parsing_a_valid_nonnullable_double : TypeParserTestsContext public override void When() { Expected = 7d; - Actual = TypeParser.Parse("7"); + Actual = Parser.Parse("7"); } [TestMethod] @@ -183,7 +183,7 @@ public class when_parsing_a_null_nullable_double : TypeParserTestsContext public override void When() { Expected = null; - Actual = TypeParser.Parse(null); + Actual = Parser.Parse(null); } [TestMethod] @@ -200,7 +200,7 @@ public class when_parsing_a_valid_nullable_double : TypeParserTestsContext public override void When() { Expected = 7d; - Actual = TypeParser.Parse("7"); + Actual = Parser.Parse("7"); } [TestMethod] @@ -217,7 +217,7 @@ public class when_parsing_a_valid_generic_nullable_double : TypeParserTestsConte public override void When() { Expected = 7d; - Actual = TypeParser.Parse("7"); + Actual = Parser.Parse("7"); } [TestMethod] @@ -234,7 +234,7 @@ public class when_parsing_a_null_nullable_int : TypeParserTestsContext public override void When() { Expected = null; - Actual = TypeParser.Parse(null); + Actual = Parser.Parse(null); } [TestMethod] @@ -251,7 +251,7 @@ public class when_parsing_a_valid_nullable_int : TypeParserTestsContext public override void When() { Expected = 7; - Actual = TypeParser.Parse("7"); + Actual = Parser.Parse("7"); } [TestMethod] @@ -268,7 +268,7 @@ public class when_parsing_a_null_nullable_date : TypeParserTestsContext public override void When() { Expected = null; - Actual = TypeParser.Parse(null); + Actual = Parser.Parse(null); } [TestMethod] @@ -285,7 +285,7 @@ public class when_parsing_a_valid_nullable_date : TypeParserTestsContext public override void When() { Expected = DateTime.Today; - Actual = TypeParser.Parse(Expected.ToString()); + Actual = Parser.Parse(Expected.ToString()); } [TestMethod] @@ -302,7 +302,7 @@ public class when_parsing_a_null_nonnullable_string : TypeParserTestsContext public override void When() { Expected = ""; - Actual = TypeParser.Parse(null, ""); + Actual = Parser.Parse(null, ""); } [TestMethod] @@ -319,7 +319,7 @@ public class when_parsing_a_valid_nonnullable_string : TypeParserTestsContext public override void When() { Expected = "test string"; - Actual = TypeParser.Parse("test string"); + Actual = Parser.Parse("test string"); } [TestMethod] @@ -336,7 +336,7 @@ public class when_parsing_a_null_nonnullable_int : TypeParserTestsContext public override void When() { Expected = 14; - Actual = TypeParser.Parse(null, 14); + Actual = Parser.Parse(null, 14); } [TestMethod] @@ -353,7 +353,7 @@ public class when_parsing_a_valid_nonnullable_int : TypeParserTestsContext public override void When() { Expected = 7; - Actual = TypeParser.Parse(7); + Actual = Parser.Parse(7); } [TestMethod] diff --git a/test/Qwiq.Core.Tests/TypeParser/TypeParserTestsContext.cs b/test/Qwiq.Core.Tests/TypeParser/TypeParserTestsContext.cs index f25fa8f3..5253c116 100644 --- a/test/Qwiq.Core.Tests/TypeParser/TypeParserTestsContext.cs +++ b/test/Qwiq.Core.Tests/TypeParser/TypeParserTestsContext.cs @@ -4,15 +4,15 @@ namespace Microsoft.Qwiq { public abstract class TypeParserTestsContext : ContextSpecification { - protected object Actual; + protected object Actual { get; set; } - protected object Expected; + protected object Expected { get; set; } - protected ITypeParser TypeParser; + protected ITypeParser Parser { get; set; } public override void Given() { - TypeParser = Qwiq.TypeParser.Default; + Parser = TypeParser.Default; } } } \ No newline at end of file From 00a9d79c4e37094e92f65b82c7d404172aa03413 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 16:38:14 -0700 Subject: [PATCH 161/251] Clean IdentityFieldValue.cs --- src/Qwiq.Core/IdentityFieldValue.cs | 37 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index ab830174..23dac135 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -95,12 +95,8 @@ public IdentityFieldValue(string displayName) { AccountName = str2; - var strArray = str2.Split(IdentityConstants.DomainAccountNameSeparator); - if (strArray.Length != 2) - { - return; - } + if (strArray.Length != 2) return; Domain = strArray[0]; LogonName = strArray[1]; @@ -123,17 +119,14 @@ public IdentityFieldValue(string displayName) } /// - /// Gets the the User principal name (UPN) or the down-level login name. + /// Gets the the User principal name (UPN) or the down-level login name. /// - /// This can be in the UPN format (e.g. UserName@Example.Microsoft.com) or the down-level logon name format (e.g. EXAMPLE\UserName). + /// + /// This can be in the UPN format (e.g. UserName@Example.Microsoft.com) or the down-level logon name format (e.g. + /// EXAMPLE\UserName). + /// public string AccountName { get; } - /// - /// Gets the user account (logon) name. - /// - /// The logon name parsed from , User Principal Name, or down-level logon name. - public string LogonName { get; } - /// /// Gets the display name. /// @@ -162,7 +155,7 @@ public IdentityFieldValue(string displayName) /// Gets the full name. /// /// The full name as determined by the descriptor identifier. - /// + /// public string Identifier { get; } /// @@ -178,13 +171,23 @@ public string IdentityName { if (!string.IsNullOrEmpty(Email)) return Email; if (!string.IsNullOrEmpty(Domain)) - return string.Format(CultureInfo.InvariantCulture, IdentityConstants.DomainQualifiedAccountNameFormat, Domain, LogonName); + return string.Format( + CultureInfo.InvariantCulture, + IdentityConstants.DomainQualifiedAccountNameFormat, + Domain, + LogonName); if (!string.IsNullOrEmpty(LogonName)) return LogonName; return null; } } + /// + /// Gets the user account (logon) name. + /// + /// The logon name parsed from , User Principal Name, or down-level logon name. + public string LogonName { get; } + public string TeamFoundationId { get; } public static explicit operator string(IdentityFieldValue value) @@ -196,8 +199,8 @@ public static explicit operator string(IdentityFieldValue value) public override string ToString() { return string.IsNullOrEmpty(IdentityName) - ? DisplayName - : $"{DisplayName} <{AccountName}>".ToString(CultureInfo.InvariantCulture); + ? DisplayName + : $"{DisplayName} <{AccountName}>".ToString(CultureInfo.InvariantCulture); } private static bool TryGetAccountName(string search, out string acccountName) From 9c0a241da1257f196cab2ff46af9c1b156dccb5d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 16:59:35 -0700 Subject: [PATCH 162/251] Add remaining comparers to Comparer.cs --- CustomDictionary.xml | 24 ++++++++++++++++ src/Qwiq.Core/Comparer.cs | 41 ++++++++++++++++++++-------- src/Qwiq.Core/GlobalSuppressions.cs | Bin 17670 -> 20086 bytes 3 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 CustomDictionary.xml diff --git a/CustomDictionary.xml b/CustomDictionary.xml new file mode 100644 index 00000000..d6f6cc69 --- /dev/null +++ b/CustomDictionary.xml @@ -0,0 +1,24 @@ + + + + + + + Tfs + + + + + + + + + + + + + + + + + diff --git a/src/Qwiq.Core/Comparer.cs b/src/Qwiq.Core/Comparer.cs index d7b19819..02ed8436 100644 --- a/src/Qwiq.Core/Comparer.cs +++ b/src/Qwiq.Core/Comparer.cs @@ -5,24 +5,43 @@ namespace Microsoft.Qwiq { public static class Comparer { - public static IEqualityComparer> FieldCollection = - ReadOnlyCollectionWithIdComparer.Default; + public static IEqualityComparer> FieldCollection { get; } = + ReadOnlyCollectionWithIdComparer.Default; - public static IEqualityComparer FieldDefinitionCollection = FieldDefinitionCollectionComparer.Default; + public static IEqualityComparer FieldDefinition { get; } = FieldDefinitionComparer.Default; - public static IEqualityComparer> Identifiable = IdentifiableComparer.Default; + public static IEqualityComparer FieldDefinitionCollection { get; } = + FieldDefinitionCollectionComparer.Default; - public static IEqualityComparer IdentityDescriptor = IdentityDescriptorComparer.Default; + public static IEqualityComparer> Identifiable { get; } = IdentifiableComparer.Default; - public static IEqualityComparer> NodeCollection = - ReadOnlyCollectionWithIdComparer.Default; + public static IEqualityComparer IdentityDescriptor { get; } = IdentityDescriptorComparer.Default; - public static IEqualityComparer OrdinalIgnoreCase = StringComparer.OrdinalIgnoreCase; + public static IEqualityComparer Node { get; } = NodeComparer.Default; - public static IEqualityComparer WorkItem = WorkItemComparer.Default; + public static IEqualityComparer> NodeCollection { get; } = + ReadOnlyCollectionWithIdComparer.Default; - public static IEqualityComparer WorkItemLinkTypeEnd = WorkItemLinkTypeEndComparer.Default; + public static IEqualityComparer> NullableIdentity { get; } = NullableIdentifiableComparer.Default; - public static IEqualityComparer WorkItemCollection = WorkItemCollectionComparer.Default; + public static IEqualityComparer OrdinalIgnoreCase { get; } = StringComparer.OrdinalIgnoreCase; + + public static IEqualityComparer Project { get; } = ProjectComparer.Default; + + public static IEqualityComparer TeamFoundationIdentity { get; } = TeamFoundationIdentityComparer.Default; + + public static IEqualityComparer WorkItem { get; } = WorkItemComparer.Default; + + public static IEqualityComparer WorkItemCollection { get; } = WorkItemCollectionComparer.Default; + + public static IEqualityComparer WorkItemLinkInfo { get; } = WorkItemLinkInfoComparer.Default; + + public static IEqualityComparer WorkItemLinkType { get; } = WorkItemLinkTypeComparer.Default; + + public static IEqualityComparer WorkItemLinkTypeEnd { get; } = WorkItemLinkTypeEndComparer.Default; + + public static IEqualityComparer WorkItemType { get; } = WorkItemTypeComparer.Default; + + public static IEqualityComparer WorkItemTypeCollection { get; } = WorkItemTypeCollectionComparer.Default; } } \ No newline at end of file diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs index eb8e44536757895faf4f0c0a12a2876164c2f691..efcf96fb2c8929208c6a3d7af3b68a5b90199fc1 100644 GIT binary patch delta 584 zcmZqcV*ECTaf6lPmJJ5Vrvt?jfqF_AiY6Cw$!ReS zkCH8A$N|Dcpciw1p7dl$0g`z@4`u>!cO-4dS^0 zHG+8gz_0@=7WV~;WP%NV&>({|8FD5US_|77GMF+z;=qxi45%j)XqhWR1<(XgJQM@@ zWk8lQ*f}6R$i`A&T;zaFOrQKwsE}6=s2XOY#^i+xTWJ#T0@6v7bA&~JfzP$s%JB>{ E01o?vi2wiq delta 14 Vcmex1hq0}Taf6lP=2c#2m;o{621Wn? From c41d64bed6ae71912b8d327405ecb802f0dc46fc Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 17:12:13 -0700 Subject: [PATCH 163/251] Remove ClientType enum --- src/Qwiq.Core.Rest/WorkItemStore.cs | 2 - src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 10 ++--- src/Qwiq.Core.Soap/WorkItemStore.cs | 2 - src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 9 ++-- src/Qwiq.Core/ClientType.cs | 28 ------------ .../Credentials/AuthenticationOptions.cs | 17 ++++--- src/Qwiq.Core/IWorkItemStore.cs | 5 --- src/Qwiq.Core/IWorkItemStoreFactory.cs | 5 +-- .../IntegrationSettings.cs | 45 +++++++++---------- .../Mocks/InstrumentedMockWorkItemStore.cs | 36 +++++++-------- test/Qwiq.Mocks/MockWorkItemStore.cs | 2 - 11 files changed, 57 insertions(+), 104 deletions(-) diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 0d9fa01e..8463c023 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -80,8 +80,6 @@ WorkItemLinkTypeCollection ValueFactory() public VssCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; - public ClientType ClientType => ClientType.Rest; - public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions.Value; public IProjectCollection Projects => _projects.Value; diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index ce27bdab..0f2742b5 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -18,7 +18,6 @@ private WorkItemStoreFactory() public IWorkItemStore Create(AuthenticationOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); - if (options.ClientType != ClientType.Rest) throw new InvalidOperationException(); var tfsProxy = (IInternalTeamProjectCollection)TfsConnectionFactory.Default.Create(options); var wis = CreateRestWorkItemStore(tfsProxy); @@ -28,9 +27,9 @@ public IWorkItemStore Create(AuthenticationOptions options) [Obsolete( "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", false)] - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default) + public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) { - return Create(endpoint, new[] { credentials }, type); + return Create(endpoint, new[] { credentials }); } [Obsolete( @@ -38,10 +37,9 @@ public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientTyp false)] public IWorkItemStore Create( Uri endpoint, - IEnumerable credentials, - ClientType type = ClientType.Default) + IEnumerable credentials) { - var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, type, types => credentials.Select(s=>s.Credentials)); + var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, types => credentials.Select(s=>s.Credentials)); return Create(options); } diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index fcf4d04e..298bae15 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -70,8 +70,6 @@ internal WorkItemStore( public int PageSize { get; } - public ClientType ClientType => ClientType.Soap; - public VssCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 228c4010..4a320324 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -24,9 +24,9 @@ public IWorkItemStore Create(AuthenticationOptions options) [Obsolete( "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", false)] - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default) + public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) { - return Create(endpoint, new[] { credentials }, type); + return Create(endpoint, new[] { credentials }); } [Obsolete( @@ -34,10 +34,9 @@ public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientTyp false)] public IWorkItemStore Create( Uri endpoint, - IEnumerable credentials, - ClientType type = ClientType.Default) + IEnumerable credentials) { - var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, type, types => credentials.Select(s=>s.Credentials)); + var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, types => credentials.Select(s=>s.Credentials)); return Create(options); } diff --git a/src/Qwiq.Core/ClientType.cs b/src/Qwiq.Core/ClientType.cs index af7ac5cb..e69de29b 100644 --- a/src/Qwiq.Core/ClientType.cs +++ b/src/Qwiq.Core/ClientType.cs @@ -1,28 +0,0 @@ -namespace Microsoft.Qwiq -{ - /// - /// Enum ClientType - /// - public enum ClientType - { - /// - /// No remote communications are made. - /// - None = 0, - - /// - /// The default client type: . - /// - Default = Soap, - - /// - /// The SOAP client integrates with Microsoft Team Foundation Server 2012 and later and Visual Studio Team Services via SOAP APIs. - /// - Soap = 1, - - /// - /// The REST client integrates with Team Foundation Server 2015 and later and Visual Studio Team Services via public REST APIs. - /// - Rest = 2 - } -} \ No newline at end of file diff --git a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs index 2e844fa0..96313e17 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs @@ -15,27 +15,30 @@ public AuthenticationOptions(string uri) { } + /// + /// Initializes a new instance of the class. + /// + /// The URI of the Team Foundation Server, including the project collection. + /// The authentication types to use against the server. + /// Type of the client. public AuthenticationOptions( string uri, - AuthenticationTypes authenticationTypes = AuthenticationTypes.All, - ClientType clientType = ClientType.Default) - : this(new Uri(uri, UriKind.Absolute), authenticationTypes, clientType) + AuthenticationTypes authenticationTypes = AuthenticationTypes.All) + : this(new Uri(uri, UriKind.Absolute), authenticationTypes) { } public AuthenticationOptions(Uri uri) - : this(uri, AuthenticationTypes.All, ClientType.Default) + : this(uri, AuthenticationTypes.All) { } public AuthenticationOptions( Uri uri, AuthenticationTypes authenticationTypes, - ClientType clientType, Func> credentialsFactory = null) { AuthenticationTypes = authenticationTypes; - ClientType = clientType; Notifications = new CredentialsNotifications(); Uri = uri ?? throw new ArgumentNullException(nameof(uri)); _createCredentials = credentialsFactory ?? CredentialsFactory; @@ -43,7 +46,7 @@ public AuthenticationOptions( public AuthenticationTypes AuthenticationTypes { get; } - public ClientType ClientType { get; } + public IEnumerable Credentials { diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 054435fb..35ba0189 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -17,11 +17,6 @@ public interface IWorkItemStore : IDisposable ITeamFoundationIdentity AuthorizedIdentity { get; } - /// - /// Indicates the communication type used for the work item store. - /// - ClientType ClientType { get; } - /// /// Returns the collection of all known s associated with this instance. /// diff --git a/src/Qwiq.Core/IWorkItemStoreFactory.cs b/src/Qwiq.Core/IWorkItemStoreFactory.cs index c5c553fd..cfb1e686 100644 --- a/src/Qwiq.Core/IWorkItemStoreFactory.cs +++ b/src/Qwiq.Core/IWorkItemStoreFactory.cs @@ -12,14 +12,13 @@ public interface IWorkItemStoreFactory [Obsolete( "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", false)] - IWorkItemStore Create(Uri endpoint, TfsCredentials credentials, ClientType type = ClientType.Default); + IWorkItemStore Create(Uri endpoint, TfsCredentials credentials); [Obsolete( "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", false)] IWorkItemStore Create( Uri endpoint, - IEnumerable credentials, - ClientType type = ClientType.Default); + IEnumerable credentials); } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index 65e6a237..d3ca1a60 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -1,57 +1,52 @@ -using System; -using System.Collections.Generic; - using Microsoft.Qwiq.Client.Rest; using Microsoft.Qwiq.Credentials; using Microsoft.VisualStudio.Services.Client; using Microsoft.VisualStudio.Services.Common; +using System; +using System.Collections.Generic; namespace Microsoft.Qwiq { /// public static class IntegrationSettings { - private static readonly Uri Uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); - /// - public static Func CreateRestStore { get; } = () => - { - var options = RestOptions; - return WorkItemStoreFactory.Default.Create(options); - }; + public static Func> Credentials = UnitTestCredentialsFactory; /// - public static Func CreateSoapStore { get; } = () => - { - var options = SoapOptions; - return Client.Soap.WorkItemStoreFactory.Default.Create(options); - }; + public static string[] Domains = { "microsoft.com" }; /// - public static AuthenticationOptions RestOptions { get; } = - new AuthenticationOptions(Uri, AuthenticationTypes.Windows, ClientType.Rest); + public static Guid ProjectGuid = Guid.Parse("8d47e068-03c8-4cdc-aa9b-fc6929290322"); /// - public static AuthenticationOptions SoapOptions { get; } = - new AuthenticationOptions(Uri, AuthenticationTypes.Windows, ClientType.Soap); + public static string TenantId = "72F988BF-86F1-41AF-91AB-2D7CD011DB47"; + + private static readonly Uri Uri = new Uri("https://microsoft.visualstudio.com/defaultcollection"); /// - public static Guid ProjectGuid = Guid.Parse("8d47e068-03c8-4cdc-aa9b-fc6929290322"); + public static AuthenticationOptions AuthenticationOptions { get; } = + new AuthenticationOptions(Uri, AuthenticationTypes.Windows); /// - public static string TenantId = "72F988BF-86F1-41AF-91AB-2D7CD011DB47"; + public static Func CreateRestStore { get; } = () => + { + var options = AuthenticationOptions; + return WorkItemStoreFactory.Default.Create(options); + }; /// - public static string[] Domains = { "microsoft.com" }; + public static Func CreateSoapStore { get; } = () => + { + var options = AuthenticationOptions; + return Client.Soap.WorkItemStoreFactory.Default.Create(options); + }; /// public static bool IsContiniousIntegrationEnvironment { get; } = Comparer.OrdinalIgnoreCase.Equals("True", Environment.GetEnvironmentVariable("CI")) || Comparer.OrdinalIgnoreCase.Equals("True", Environment.GetEnvironmentVariable("APPVEYOR")); - /// - public static Func> Credentials = UnitTestCredentialsFactory; - private static IEnumerable UnitTestCredentialsFactory(AuthenticationTypes types) { if (types.HasFlag(AuthenticationTypes.Windows)) diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index 8fad6f99..e364d0b6 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -14,25 +14,9 @@ public InstrumentedMockWorkItemStore(IWorkItemStore innerWorkItemStore) _innerWorkItemStore = innerWorkItemStore; } - public int ProjectsCallCount { get; private set; } - - public int QueryCallCount => QueryIdCallCount + QueryIdsCallCount + QueryLinksCallCount + QueryStringCallCount; - - public int QueryIdCallCount { get; private set; } - - public int QueryIdsCallCount { get; private set; } - - public int QueryLinksCallCount { get; private set; } - - public int QueryStringCallCount { get; private set; } - - public int TeamProjectCollectionCallCount { get; private set; } - - public int WorkItemLinkTypesCallCount { get; private set; } - public VssCredentials AuthorizedCredentials => _innerWorkItemStore.AuthorizedCredentials; - public ClientType ClientType => _innerWorkItemStore.ClientType; + public ITeamFoundationIdentity AuthorizedIdentity => _innerWorkItemStore?.AuthorizedIdentity; public IFieldDefinitionCollection FieldDefinitions => _innerWorkItemStore.FieldDefinitions; @@ -45,6 +29,18 @@ public IProjectCollection Projects } } + public int ProjectsCallCount { get; private set; } + + public int QueryCallCount => QueryIdCallCount + QueryIdsCallCount + QueryLinksCallCount + QueryStringCallCount; + + public int QueryIdCallCount { get; private set; } + + public int QueryIdsCallCount { get; private set; } + + public int QueryLinksCallCount { get; private set; } + + public int QueryStringCallCount { get; private set; } + public IRegisteredLinkTypeCollection RegisteredLinkTypes => _innerWorkItemStore.RegisteredLinkTypes; public ITeamProjectCollection TeamProjectCollection @@ -56,9 +52,9 @@ public ITeamProjectCollection TeamProjectCollection } } - public TimeZone TimeZone => _innerWorkItemStore.TimeZone; + public int TeamProjectCollectionCallCount { get; private set; } - public ITeamFoundationIdentity AuthorizedIdentity => _innerWorkItemStore?.AuthorizedIdentity; + public TimeZone TimeZone => _innerWorkItemStore.TimeZone; public IWorkItemLinkTypeCollection WorkItemLinkTypes { @@ -69,6 +65,8 @@ public IWorkItemLinkTypeCollection WorkItemLinkTypes } } + public int WorkItemLinkTypesCallCount { get; private set; } + public void Dispose() { _innerWorkItemStore.Dispose(); diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 097f2d34..da336a0c 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -60,8 +60,6 @@ Func queryFactory public VssCredentials AuthorizedCredentials => null; - public ClientType ClientType => ClientType.None; - public IFieldDefinitionCollection FieldDefinitions => _storeDefinitions.Value; public IProjectCollection Projects => _projects.Value; From 383c7c2c95dc133e4074469ac57dc3311cb197e4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 17:15:25 -0700 Subject: [PATCH 164/251] Clean WorkItemStore.cs --- src/Qwiq.Core.Soap/WorkItemStore.cs | 63 ++++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index 298bae15..2f76a46e 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -1,11 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.Common; using Microsoft.VisualStudio.Services.Common; - +using System; +using System.Collections.Generic; +using System.Linq; using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Client.Soap @@ -16,17 +14,17 @@ namespace Microsoft.Qwiq.Client.Soap /// internal class WorkItemStore : IWorkItemStore { - private readonly Lazy _workItemLinkTypes; - private readonly Lazy _linkTypes; + private readonly Lazy _projects; + private readonly Lazy _queryFactory; private readonly Lazy _tfs; - private readonly Lazy _workItemStore; + private readonly Lazy _workItemLinkTypes; - private readonly Lazy _projects; + private readonly Lazy _workItemStore; internal WorkItemStore( Func tpcFactory, @@ -38,22 +36,28 @@ internal WorkItemStore( if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); - if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) - throw new PageSizeRangeException(); + if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) throw new PageSizeRangeException(); _tfs = new Lazy(tpcFactory); _workItemStore = new Lazy(wisFactory); _queryFactory = new Lazy(() => queryFactory(this)); - _workItemLinkTypes = new Lazy( - () => - { - return new WorkItemLinkTypeCollection( - _workItemStore.Value.WorkItemLinkTypes.Select(item => new WorkItemLinkType(item))); - }); + IWorkItemLinkTypeCollection WorkItemLinkTypeCollectionFactory() + { + return new WorkItemLinkTypeCollection(_workItemStore.Value.WorkItemLinkTypes.Select(item => new WorkItemLinkType(item))); + } + _workItemLinkTypes = new Lazy(WorkItemLinkTypeCollectionFactory); - _linkTypes = new Lazy(() => new RegisteredLinkTypeCollection(_workItemStore.Value.RegisteredLinkTypes.OfType().Select(item => new RegisteredLinkType(item.Name)))); + IRegisteredLinkTypeCollection RegisteredLinkTypeCollectionFactory() + { + return new RegisteredLinkTypeCollection( + _workItemStore + .Value.RegisteredLinkTypes.OfType() + .Select(item => new RegisteredLinkType(item.Name))); + } + + _linkTypes = new Lazy(RegisteredLinkTypeCollectionFactory); _projects = new Lazy(() => new ProjectCollection(_workItemStore.Value.Projects)); @@ -68,26 +72,31 @@ internal WorkItemStore( { } - public int PageSize { get; } - public VssCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; - internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; + public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection?.AuthorizedIdentity; - public IFieldDefinitionCollection FieldDefinitions => ExceptionHandlingDynamicProxyFactory - .Create( - new FieldDefinitionCollection(_workItemStore.Value.FieldDefinitions)); + public IFieldDefinitionCollection FieldDefinitions => ExceptionHandlingDynamicProxyFactory.Create( + new + FieldDefinitionCollection( + _workItemStore + .Value + .FieldDefinitions)); + + public int PageSize { get; } public IProjectCollection Projects => _projects.Value; + public IRegisteredLinkTypeCollection RegisteredLinkTypes => _linkTypes.Value; + public ITeamProjectCollection TeamProjectCollection => _tfs.Value; public TimeZone TimeZone => _workItemStore.Value.TimeZone; - public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection?.AuthorizedIdentity; - public IWorkItemLinkTypeCollection WorkItemLinkTypes => _workItemLinkTypes.Value; + internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; + public void Dispose() { Dispose(true); @@ -142,8 +151,6 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision } } - public IRegisteredLinkTypeCollection RegisteredLinkTypes => _linkTypes.Value; - protected virtual void Dispose(bool disposing) { if (disposing) if (_tfs.IsValueCreated) _tfs.Value?.Dispose(); From 2b16e9b8c1f7c1570065094d16982396205d9ad5 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 17:33:47 -0700 Subject: [PATCH 165/251] Clean AuthenticationOptions.cs --- .../Credentials/AuthenticationOptions.cs | 52 +++++++++++++----- src/Qwiq.Core/GlobalSuppressions.cs | Bin 20086 -> 21410 bytes 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs index 96313e17..d69ec47f 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs @@ -1,42 +1,66 @@ -using System; -using System.Collections.Generic; - using Microsoft.VisualStudio.Services.Client; using Microsoft.VisualStudio.Services.Common; +using System; +using System.Collections.Generic; namespace Microsoft.Qwiq.Credentials { + /// + /// Represents options for authentication against a Team Foundation Server. + /// public class AuthenticationOptions { private readonly Func> _createCredentials; + /// + /// Initializes a new instance of the class. + /// + /// The URI of the Team Foundation Server, including the project collection. public AuthenticationOptions(string uri) - : this(new Uri(uri, UriKind.Absolute)) + : this(uri, AuthenticationTypes.All) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The URI of the Team Foundation Server, including the project collection. /// The authentication types to use against the server. - /// Type of the client. - public AuthenticationOptions( - string uri, - AuthenticationTypes authenticationTypes = AuthenticationTypes.All) + public AuthenticationOptions(string uri, AuthenticationTypes authenticationTypes) : this(new Uri(uri, UriKind.Absolute), authenticationTypes) { } + /// + /// Initializes a new instance of the class. + /// + /// The URI of the Team Foundation Server, including the project collection. public AuthenticationOptions(Uri uri) : this(uri, AuthenticationTypes.All) { } + /// + /// Initializes a new instance of the class. + /// + /// The URI of the Team Foundation Server, including the project collection. + /// The authentication types. + public AuthenticationOptions(Uri uri, AuthenticationTypes authenticationTypes) + : this(uri, authenticationTypes, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The URI of the Team Foundation Server, including the project collection. + /// The authentication types. + /// The credentials factory. + /// uri public AuthenticationOptions( Uri uri, AuthenticationTypes authenticationTypes, - Func> credentialsFactory = null) + Func> credentialsFactory) { AuthenticationTypes = authenticationTypes; Notifications = new CredentialsNotifications(); @@ -46,8 +70,6 @@ public AuthenticationOptions( public AuthenticationTypes AuthenticationTypes { get; } - - public IEnumerable Credentials { get @@ -80,17 +102,19 @@ private static IEnumerable CredentialsFactory(AuthenticationType if (t.HasFlag(AuthenticationTypes.Windows)) { + var storage = new VssClientCredentialStorage(); + // User did not specify a username or a password, so use the process identity yield return new VssClientCredentials(new WindowsCredential(false)) { - Storage = new VssClientCredentialStorage(), + Storage = storage, PromptType = CredentialPromptType.DoNotPrompt }; // Use the Windows identity of the logged on user yield return new VssClientCredentials(true) { - Storage = new VssClientCredentialStorage(), + Storage = storage, PromptType = CredentialPromptType.PromptIfNeeded }; } diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs index efcf96fb2c8929208c6a3d7af3b68a5b90199fc1..1a5b47ba4024eb4efc60b5356333d2fb24c2ea32 100644 GIT binary patch delta 123 zcmex1hjGzz#to~yCf|`;F}c8b)8qn`xXDWDX_L(~_%@$XKf*Zq1dEitF@p(%6+-|+ zB116{1~XIw$r6TChFpeFh9ZVc27iVmhAbdI87NxJ;K`5&l}}_y0m`LKzOO7g`4mS4 R(D1Cubp||}S9zUb1^~FvC4~S0 delta 14 WcmZ3qoblTn#to~yHed5U!wdj7Wd|t$ From 1b7a65e3d3158453b668c76173fb9ef77b59c71e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 17:39:27 -0700 Subject: [PATCH 166/251] Clean DeniedOrNotExistException.cs --- .../Exceptions/DeniedOrNotExistException.cs | 33 +++++++++++++++++- src/Qwiq.Core/GlobalSuppressions.cs | Bin 21410 -> 21890 bytes 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs b/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs index 7a565f56..239bc251 100644 --- a/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs +++ b/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs @@ -1,30 +1,61 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.Serialization; namespace Microsoft.Qwiq { + /// + /// Represents a failure to load a team foundation resource. + /// + /// [Serializable] public class DeniedOrNotExistException : Exception { private const string ErrorFormat = "TF26193: The team project {0} does not exist. Check the team project name and try again."; + /// + /// Initializes a new instance of the class. + /// public DeniedOrNotExistException() : base("TF237090: Does not exist or access is denied.") { } + /// + /// Initializes a new instance of the class. + /// + /// Name of the project. public DeniedOrNotExistException(string projectName) : base(string.Format(CultureInfo.InvariantCulture, ErrorFormat, projectName)) { } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")] + /// + /// Initializes a new instance of the class. + /// + /// The project unique identifier. + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")] public DeniedOrNotExistException(Guid projectGuid) : base(string.Format(CultureInfo.InvariantCulture, ErrorFormat, projectGuid)) { } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + internal DeniedOrNotExistException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. protected DeniedOrNotExistException(SerializationInfo info, StreamingContext context) : base(info, context) { diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs index 1a5b47ba4024eb4efc60b5356333d2fb24c2ea32..7a15114bd39b9fbf96548cbf639c9553d9b8647a 100644 GIT binary patch delta 178 zcmZ3qoUv&& Date: Wed, 26 Apr 2017 17:45:30 -0700 Subject: [PATCH 167/251] Provide text in Debug.Assert --- src/Qwiq.Core.Rest/LinkCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index 88fce316..01acb377 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -50,7 +50,7 @@ public LinkCollection(IEnumerable relations, Func Date: Wed, 26 Apr 2017 17:47:20 -0700 Subject: [PATCH 168/251] Clean LinkCollection.cs --- src/Qwiq.Core.Rest/LinkCollection.cs | 84 ++++++++++++---------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index 01acb377..dc1555ce 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -11,18 +11,15 @@ internal class LinkCollection : ReadOnlyCollection, ICollection { public LinkCollection(IEnumerable relations, Func linkFunc) { - if (relations == null) - throw new ArgumentNullException(nameof(relations)); - if (linkFunc == null) - throw new ArgumentNullException(nameof(linkFunc)); + if (relations == null) throw new ArgumentNullException(nameof(relations)); + if (linkFunc == null) throw new ArgumentNullException(nameof(linkFunc)); foreach (var relation in relations) { if (CoreLinkTypeReferenceNames.Related.Equals(relation.Rel, StringComparison.OrdinalIgnoreCase)) { // Last part of the Url is the ID - var lte = linkFunc(relation.Rel) - .ForwardEnd; + var lte = linkFunc(relation.Rel).ForwardEnd; var l = new RelatedLink(ExtractId(relation.Url), lte, ExtractComment(relation.Attributes)); Add(l); } @@ -33,12 +30,14 @@ public LinkCollection(IEnumerable relations, Func relations, Func relations, Func relationAttributes) - { - return ExtractProperty(relationAttributes, "comment"); - } - - private static string ExtractProperty(IDictionary relationAttributes, string property) - { - relationAttributes.TryGetValue(property, out object val); - return val?.ToString(); - } - public bool IsReadOnly => true; public void CopyTo(ILink[] array, int arrayIndex) { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - if (array.Rank != 1) - { - throw new ArgumentException(nameof(array)); - } - if (arrayIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - if ((array.Length - arrayIndex) < Count) - { - throw new ArgumentException(nameof(array)); - } - foreach (var value in this) - { - array.SetValue(value, arrayIndex++); - } + if (array == null) throw new ArgumentNullException(nameof(array)); + if (array.Rank != 1) throw new ArgumentException(nameof(array)); + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + if (array.Length - arrayIndex < Count) throw new ArgumentException(nameof(array)); + foreach (var value in this) array.SetValue(value, arrayIndex++); } void ICollection.Add(ILink item) @@ -120,5 +89,22 @@ bool ICollection.Remove(ILink item) { throw new NotSupportedException(); } + + private static string ExtractComment(IDictionary relationAttributes) + { + return ExtractProperty(relationAttributes, "comment"); + } + + private static int ExtractId(string uri) + { + var arr = uri.Split('/'); + return Convert.ToInt32(arr.Last()); + } + + private static string ExtractProperty(IDictionary relationAttributes, string property) + { + relationAttributes.TryGetValue(property, out object val); + return val?.ToString(); + } } } \ No newline at end of file From fbea0efa4812d7ecc1b22c0b1f016ae492792596 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 17:57:19 -0700 Subject: [PATCH 169/251] Clean FieldDefinitionNotExistException.cs --- .../FieldDefinitionNotExistException.cs | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs b/src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs index d20773e9..564e05e3 100644 --- a/src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs +++ b/src/Qwiq.Core/Exceptions/FieldDefinitionNotExistException.cs @@ -1,20 +1,54 @@ using System; +using System.Globalization; using System.Runtime.Serialization; namespace Microsoft.Qwiq { + /// + /// An exception that is thrown when a field definition does not exist on a work item type. + /// + /// [Serializable] public class FieldDefinitionNotExistException : InvalidOperationException { + /// + /// Initializes a new instance of the class. + /// + public FieldDefinitionNotExistException() + : base( + "TF26028: A field definition in the work item type definition file does not exist. Add a definition for this field, or remove the reference to the field and try again.") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The field definition name. public FieldDefinitionNotExistException(string name) - : base($"TF26027: A field definition {name} in the work item type definition file does not exist. Add a definition for this field or remove the reference to the field and try again.") + : base( + $"TF26027: A field definition {name} in the work item type definition file does not exist. Add a definition for this field or remove the reference to the field and try again." + .ToString(CultureInfo.InvariantCulture)) { + } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference (Nothing in Visual Basic), the current exception is raised in a catch block that handles the inner exception. + public FieldDefinitionNotExistException(string message, Exception innerException) + : base(message, innerException) + { } + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. protected FieldDefinitionNotExistException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} +} \ No newline at end of file From b3fb0637d45cafd65643b089e90c584c2658c3de Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 18:17:09 -0700 Subject: [PATCH 170/251] DRY out WorkItemStoreFactory.cs --- src/Qwiq.Core.Rest/WorkItemStoreFactory.cs | 42 +++++------------- src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 44 +++++-------------- src/Qwiq.Core/Qwiq.Core.csproj | 1 + src/Qwiq.Core/WorkItemStoreFactory.cs | 26 +++++++++++ .../IntegrationSettings.cs | 2 +- ...orkItemStoreFactoryContextSpecification.cs | 2 +- 6 files changed, 50 insertions(+), 67 deletions(-) create mode 100644 src/Qwiq.Core/WorkItemStoreFactory.cs diff --git a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs index 0f2742b5..3231e621 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreFactory.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Qwiq.Credentials; using Microsoft.Qwiq.Exceptions; namespace Microsoft.Qwiq.Client.Rest { - public class WorkItemStoreFactory : IWorkItemStoreFactory + public class WorkItemStoreFactory : Qwiq.WorkItemStoreFactory { public static readonly IWorkItemStoreFactory Default = Nested.Instance; @@ -15,7 +13,13 @@ private WorkItemStoreFactory() { } - public IWorkItemStore Create(AuthenticationOptions options) + [Obsolete("This method is deprecated and will be removed in a future release. See property Default instead.", false)] + public static IWorkItemStoreFactory GetInstance() + { + return Default; + } + + public override IWorkItemStore Create(AuthenticationOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -24,33 +28,6 @@ public IWorkItemStore Create(AuthenticationOptions options) return ExceptionHandlingDynamicProxyFactory.Create(wis); } - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) - { - return Create(endpoint, new[] { credentials }); - } - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - public IWorkItemStore Create( - Uri endpoint, - IEnumerable credentials) - { - var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, types => credentials.Select(s=>s.Credentials)); - return Create(options); - } - - [Obsolete( - "This method is deprecated and will be removed in a future release. See property Default instead.", - false)] - public static IWorkItemStoreFactory GetInstance() - { - return Default; - } - private static IWorkItemStore CreateRestWorkItemStore(IInternalTeamProjectCollection tfs) { return new WorkItemStore(() => tfs, QueryFactory.GetInstance); @@ -58,10 +35,11 @@ private static IWorkItemStore CreateRestWorkItemStore(IInternalTeamProjectCollec // ReSharper disable ClassNeverInstantiated.Local private class Nested - // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore ClassNeverInstantiated.Local { // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemStoreFactory Instance = new WorkItemStoreFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 4a320324..1884cad9 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Qwiq.Credentials; namespace Microsoft.Qwiq.Client.Soap { - public class WorkItemStoreFactory : IWorkItemStoreFactory + public class WorkItemStoreFactory : Qwiq.WorkItemStoreFactory { public static readonly IWorkItemStoreFactory Default = Nested.Instance; @@ -14,51 +12,31 @@ private WorkItemStoreFactory() { } - public IWorkItemStore Create(AuthenticationOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - var tfsProxy = (IInternalTeamProjectCollection)TfsConnectionFactory.Default.Create(options); - return CreateSoapWorkItemStore(tfsProxy); - } - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - public IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) - { - return Create(endpoint, new[] { credentials }); - } - - [Obsolete( - "This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", - false)] - public IWorkItemStore Create( - Uri endpoint, - IEnumerable credentials) + [Obsolete("This method is deprecated and will be removed in a future release. See property Default instead.", false)] + public static IWorkItemStoreFactory GetInstance() { - var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, types => credentials.Select(s=>s.Credentials)); - return Create(options); + return Default; } - [Obsolete( - "This method is deprecated and will be removed in a future release. See property Default instead.", - false)] - public static IWorkItemStoreFactory GetInstance() + public override IWorkItemStore Create(AuthenticationOptions options) { - return Default; + if (options == null) throw new ArgumentNullException(nameof(options)); + var tfsProxy = (IInternalTeamProjectCollection)TfsConnectionFactory.Default.Create(options); + return CreateSoapWorkItemStore(tfsProxy); } private static IWorkItemStore CreateSoapWorkItemStore(IInternalTeamProjectCollection tfs) { - return new WorkItemStore(() => tfs, store => new QueryFactory(store)); + return new WorkItemStore(() => tfs, store => new QueryFactory(store)); } // ReSharper disable ClassNeverInstantiated.Local private class Nested - // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore ClassNeverInstantiated.Local { // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly WorkItemStoreFactory Instance = new WorkItemStoreFactory(); + // ReSharper restore MemberHidesStaticFromOuterClass // Explicit static constructor to tell C# compiler diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 84ffbea4..4b2e0e07 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -204,6 +204,7 @@ + diff --git a/src/Qwiq.Core/WorkItemStoreFactory.cs b/src/Qwiq.Core/WorkItemStoreFactory.cs new file mode 100644 index 00000000..be5998b5 --- /dev/null +++ b/src/Qwiq.Core/WorkItemStoreFactory.cs @@ -0,0 +1,26 @@ +using Microsoft.Qwiq.Credentials; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq +{ + public abstract class WorkItemStoreFactory : IWorkItemStoreFactory + { + /// + public abstract IWorkItemStore Create(AuthenticationOptions options); + + [Obsolete("This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", false)] + public virtual IWorkItemStore Create(Uri endpoint, TfsCredentials credentials) + { + return Create(endpoint, new[] { credentials }); + } + + [Obsolete("This method is deprecated and will be removed in a future release. See Create(AuthenticationOptions) instead.", false)] + public virtual IWorkItemStore Create(Uri endpoint, IEnumerable credentials) + { + var options = new AuthenticationOptions(endpoint, AuthenticationTypes.Windows, types => credentials.Select(s => s.Credentials)); + return Create(options); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index d3ca1a60..72a9bcfd 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -32,7 +32,7 @@ public static class IntegrationSettings public static Func CreateRestStore { get; } = () => { var options = AuthenticationOptions; - return WorkItemStoreFactory.Default.Create(options); + return Client.Rest.WorkItemStoreFactory.Default.Create(options); }; /// diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs index ffc0ba1a..b4c2d5bf 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs @@ -11,7 +11,7 @@ public abstract class WorkItemStoreFactoryContextSpecification : TimedContextSpe public override void Given() { - Instance = WorkItemStoreFactory.Default; + Instance = Client.Soap.WorkItemStoreFactory.Default; WorkItemStore = TimedAction(Create, "SOAP", "WIS Create"); base.Given(); } From a203889bf22a9f4d2cc2d341b21cf77a84616b86 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 18:06:47 -0700 Subject: [PATCH 171/251] Code clean up Combine argument check with assignment --- src/Qwiq.Core/Node.cs | 3 +-- src/Qwiq.Core/ReadOnlyCollection.cs | 9 +++------ src/Qwiq.Linq/Query.cs | 3 +-- test/Qwiq.Mocks/MockQueryByWiql.cs | 4 +--- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Qwiq.Core/Node.cs b/src/Qwiq.Core/Node.cs index ad2aad58..9cb9d058 100644 --- a/src/Qwiq.Core/Node.cs +++ b/src/Qwiq.Core/Node.cs @@ -22,7 +22,6 @@ internal Node( Func parentFactory, Func> childrenFactory) { - if (name == null) throw new ArgumentNullException(nameof(name)); if (uri == null) throw new ArgumentNullException(nameof(uri)); if (parentFactory == null) throw new ArgumentNullException(nameof(parentFactory)); if (childrenFactory == null) throw new ArgumentNullException(nameof(childrenFactory)); @@ -30,7 +29,7 @@ internal Node( Id = id; IsAreaNode = isAreaNode; IsIterationNode = isIterationNode; - Name = name; + Name = name ?? throw new ArgumentNullException(nameof(name)); Uri = uri; _parent = new Lazy(parentFactory); diff --git a/src/Qwiq.Core/ReadOnlyCollection.cs b/src/Qwiq.Core/ReadOnlyCollection.cs index 70632413..73ad42dd 100644 --- a/src/Qwiq.Core/ReadOnlyCollection.cs +++ b/src/Qwiq.Core/ReadOnlyCollection.cs @@ -26,10 +26,8 @@ public abstract class ReadOnlyCollection : IReadOnlyObjectList protected ReadOnlyCollection(Func> itemFactory, Func nameFunc) { - if (itemFactory == null) throw new ArgumentNullException(nameof(itemFactory)); - if (nameFunc == null) throw new ArgumentNullException(nameof(nameFunc)); - ItemFactory = itemFactory; - _nameFunc = nameFunc; + ItemFactory = itemFactory ?? throw new ArgumentNullException(nameof(itemFactory)); + _nameFunc = nameFunc ?? throw new ArgumentNullException(nameof(nameFunc)); } protected ReadOnlyCollection(IEnumerable items, Func nameFunc) @@ -39,9 +37,8 @@ protected ReadOnlyCollection(IEnumerable items, Func nameFunc) } protected ReadOnlyCollection(IEnumerable items) - :this(items, null) + : this(items, null) { - } protected ReadOnlyCollection() diff --git a/src/Qwiq.Linq/Query.cs b/src/Qwiq.Linq/Query.cs index 336882c2..7d2131bc 100644 --- a/src/Qwiq.Linq/Query.cs +++ b/src/Qwiq.Linq/Query.cs @@ -27,11 +27,10 @@ public Query(IQueryProvider provider, IWiqlQueryBuilder builder) public Query(IQueryProvider provider, IWiqlQueryBuilder builder, Expression expression) { - if (provider == null) throw new ArgumentNullException(nameof(provider)); if (expression == null) throw new ArgumentNullException(nameof(expression)); if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) throw new ArgumentOutOfRangeException(nameof(expression)); - _provider = provider; + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); _builder = builder; _expression = expression; } diff --git a/test/Qwiq.Mocks/MockQueryByWiql.cs b/test/Qwiq.Mocks/MockQueryByWiql.cs index ab6572b3..706f1429 100644 --- a/test/Qwiq.Mocks/MockQueryByWiql.cs +++ b/test/Qwiq.Mocks/MockQueryByWiql.cs @@ -24,12 +24,10 @@ public MockQueryByWiql(IEnumerable ids, string query, MockWorkItemStore sto public MockQueryByWiql(string query, MockWorkItemStore store) { - if (store == null) - throw new ArgumentNullException(nameof(store)); if (string.IsNullOrWhiteSpace(query)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(query)); - _store = store; + _store = store ?? throw new ArgumentNullException(nameof(store)); _parts = new List>(); foreach (Match m in EqualsRegex.Matches(query)) From 84dc5b61ed9f8881126d4eb80c83e1c67ee49a43 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 18:41:59 -0700 Subject: [PATCH 172/251] Code clean up --- .../IntegrationSettings.cs | 1 - ...orkItemStoreFactoryContextSpecification.cs | 22 ++++++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index 72a9bcfd..80d21276 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -1,4 +1,3 @@ -using Microsoft.Qwiq.Client.Rest; using Microsoft.Qwiq.Credentials; using Microsoft.VisualStudio.Services.Client; using Microsoft.VisualStudio.Services.Common; diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs index b4c2d5bf..7a487f40 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/Soap/WorkItemStoreFactoryContextSpecification.cs @@ -1,5 +1,4 @@ -using Microsoft.Qwiq.Client.Soap; -using Microsoft.Qwiq.Tests.Common; +using Microsoft.Qwiq.Tests.Common; namespace Microsoft.Qwiq.WorkItemStore.Soap { @@ -9,23 +8,20 @@ public abstract class WorkItemStoreFactoryContextSpecification : TimedContextSpe protected IWorkItemStore WorkItemStore { get; private set; } - public override void Given() - { - Instance = Client.Soap.WorkItemStoreFactory.Default; - WorkItemStore = TimedAction(Create, "SOAP", "WIS Create"); - base.Given(); - } - - - public override void Cleanup() { - TimedAction(()=> WorkItemStore?.Dispose(), "SOAP", "WIS Dispose"); + TimedAction(() => WorkItemStore?.Dispose(), "SOAP", "WIS Dispose"); base.Cleanup(); - } public abstract IWorkItemStore Create(); + + public override void Given() + { + Instance = Client.Soap.WorkItemStoreFactory.Default; + WorkItemStore = TimedAction(Create, "SOAP", "WIS Create"); + base.Given(); + } } } \ No newline at end of file From 9f81c5d41ba40443050cb0255e236c728b3485a3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 18:43:02 -0700 Subject: [PATCH 173/251] Remove empty file --- src/Qwiq.Core/ClientType.cs | 0 src/Qwiq.Core/Qwiq.Core.csproj | 1 - 2 files changed, 1 deletion(-) delete mode 100644 src/Qwiq.Core/ClientType.cs diff --git a/src/Qwiq.Core/ClientType.cs b/src/Qwiq.Core/ClientType.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 4b2e0e07..5776dd73 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -66,7 +66,6 @@ - From 243b4cc6c51eb389472ba160779e9cbdaea6c6c8 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 18:50:12 -0700 Subject: [PATCH 174/251] Fix invalid documentation element --- src/Qwiq.Core/Exceptions/ProxyBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Core/Exceptions/ProxyBase.cs b/src/Qwiq.Core/Exceptions/ProxyBase.cs index 5c1fda20..23b4f2b5 100644 --- a/src/Qwiq.Core/Exceptions/ProxyBase.cs +++ b/src/Qwiq.Core/Exceptions/ProxyBase.cs @@ -19,7 +19,7 @@ public class ProxyBase /// /// /// - /// Intercepted interfaces can provide IEquatable<> implementations, but that will not forward calls to + /// Intercepted interfaces can provide IEquatable`1 implementations, but that will not forward calls to /// Object.Equals(Object) or Object.GetHashCode() since neither is part of the interface. /// public override bool Equals(object obj) From 1e4f8b1dfe8ac8204d2a4412f640c87337633ff7 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 19:14:22 -0700 Subject: [PATCH 175/251] Code Cleanup Documentation updates --- CustomDictionary.xml | 1 + Qwiq.sln.GhostDoc.user.dic | 1 + src/Qwiq.Core/GlobalSuppressions.cs | Bin 21890 -> 22744 bytes src/Qwiq.Core/IdentityFieldValue.cs | 14 ++++++++++---- src/Qwiq.Core/ValidationState.cs | 2 +- src/Qwiq.Core/WorkItemLinkInfo.cs | 3 ++- src/Qwiq.Core/WorkItemLinkType.cs | 5 +++-- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CustomDictionary.xml b/CustomDictionary.xml index d6f6cc69..4529d0e2 100644 --- a/CustomDictionary.xml +++ b/CustomDictionary.xml @@ -5,6 +5,7 @@ Tfs + wiql diff --git a/Qwiq.sln.GhostDoc.user.dic b/Qwiq.sln.GhostDoc.user.dic index ecb34c25..74a3c345 100644 --- a/Qwiq.sln.GhostDoc.user.dic +++ b/Qwiq.sln.GhostDoc.user.dic @@ -1,3 +1,4 @@ Lavallee pelavall Qwiq +wiql diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs index 7a15114bd39b9fbf96548cbf639c9553d9b8647a..4420acc1525fbef042f37000eddbdb702886cd02 100644 GIT binary patch delta 200 zcmZo#&3I!YMH`vB`_E<=rJfWxBz)+K+#f$9EOs~7v(lj zej_^1I|L}22vqL|)Kv;JEd^>{K9KDR @@ -33,8 +32,9 @@ public class IdentityFieldValue /// Initializes a new instance of the class. ///

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 - + {57407CCA-8444-4713-95E9-CFC1168D846B} Library @@ -256,11 +256,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj index 286d9de0..ac0447f8 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj @@ -1,8 +1,8 @@  - + {26DFD8E0-C486-4EAE-BA7F-277A40AF5CC2} @@ -177,6 +177,7 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. @@ -186,6 +187,5 @@ - \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj index f027697e..1fdc6ef1 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj @@ -1,8 +1,8 @@  - + {CE68530E-EB8F-4BE2-9563-A09AC70EA8C1} Library @@ -236,11 +236,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 0dc08fb6..fa4cae52 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -1,8 +1,8 @@  - + {E4130432-C890-41E0-8407-C4142CAF59D8} Library @@ -275,11 +275,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj index 2dad5b0b..d7bf038e 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj @@ -1,8 +1,8 @@  - + {0A26136B-A086-4CDF-BFF6-E0A83FB67446} Library diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj index 29a26199..93867477 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj @@ -1,8 +1,8 @@  - + {DB2C0AC6-CF65-412E-93A3-D3F51857BED9} @@ -179,6 +179,7 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. @@ -188,6 +189,5 @@ - \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj index 8894590b..e9ca8384 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj @@ -1,8 +1,8 @@  - + {BECF2066-A6C5-475A-BA14-12890F284A12} Library @@ -109,4 +109,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 66a8bb90..f14f2cd2 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -1,7 +1,7 @@  - + {DB07E690-4B77-414F-91C7-1A48C9F01F24} diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index 554ee3fb..8c5da6a7 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -1,8 +1,8 @@  - + {B45C92B0-AC36-409D-86A5-5428C87384C3} From c526fa7e5c0c5e710b019694bf370c6f9049fec9 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 11:18:46 -0700 Subject: [PATCH 155/251] Code clean up --- src/Qwiq.Core/IdentityFieldValue.cs | 56 ++++---- .../MockIdentityManagementService.cs | 124 +++++++++--------- 2 files changed, 87 insertions(+), 93 deletions(-) diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index 607b147f..b8abd5a0 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq { ///

/// The identity. + /// identity public IdentityFieldValue(ITeamFoundationIdentity identity) - : this(identity.DisplayName, identity.Descriptor.Identifier, identity.TeamFoundationId.ToString()) + : this(identity?.DisplayName, identity?.Descriptor?.Identifier, identity?.TeamFoundationId.ToString()) { if (identity == null) throw new ArgumentNullException(nameof(identity)); } @@ -190,8 +190,14 @@ public string IdentityName public string TeamFoundationId { get; } + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// If is null, null; otherwise, . public static explicit operator string(IdentityFieldValue value) { + if (value == null) return null; return value.IdentityName; } diff --git a/src/Qwiq.Core/ValidationState.cs b/src/Qwiq.Core/ValidationState.cs index 7dbc4400..b1cb0a94 100644 --- a/src/Qwiq.Core/ValidationState.cs +++ b/src/Qwiq.Core/ValidationState.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq /// ///
/// - /// See + /// See /// public enum ValidationState { diff --git a/src/Qwiq.Core/WorkItemLinkInfo.cs b/src/Qwiq.Core/WorkItemLinkInfo.cs index d89f4e31..4108133a 100644 --- a/src/Qwiq.Core/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core/WorkItemLinkInfo.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Globalization; namespace Microsoft.Qwiq { @@ -60,7 +61,7 @@ public override int GetHashCode() public override string ToString() { - return $"S:{SourceId} T:{TargetId} Type:{LinkType}"; + return $"S:{SourceId} T:{TargetId} Type:{LinkType}".ToString(CultureInfo.InvariantCulture); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkType.cs b/src/Qwiq.Core/WorkItemLinkType.cs index 2c503fec..a2ee48eb 100644 --- a/src/Qwiq.Core/WorkItemLinkType.cs +++ b/src/Qwiq.Core/WorkItemLinkType.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Microsoft.Qwiq { @@ -67,13 +68,13 @@ public override string ToString() internal void SetReverseEnd(IWorkItemLinkTypeEnd value) { - if (_reverse != null) throw new InvalidOperationException($"{nameof(ReverseEnd)} already contains a value."); + if (_reverse != null) throw new InvalidOperationException($"{nameof(ReverseEnd)} already contains a value.".ToString(CultureInfo.InvariantCulture)); _reverse = value ?? throw new ArgumentNullException(nameof(value)); } internal void SetForwardEnd(IWorkItemLinkTypeEnd value) { - if (_forward != null) throw new InvalidOperationException($"{nameof(ForwardEnd)} already contains a value."); + if (_forward != null) throw new InvalidOperationException($"{nameof(ForwardEnd)} already contains a value.".ToString(CultureInfo.InvariantCulture)); _forward = value ?? throw new ArgumentNullException(nameof(value)); } From 3db5fad09ffb739b2576a6378acacef54e9e724b Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 26 Apr 2017 19:16:12 -0700 Subject: [PATCH 176/251] Add CA1704 suppressions for known good names --- src/Qwiq.Core/GlobalSuppressions.cs | Bin 22744 -> 25170 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs index 4420acc1525fbef042f37000eddbdb702886cd02..357a4120a409d083f585508613da6fcf6965ce9b 100644 GIT binary patch delta 227 zcmcbyk@37F@h8+;!wok!;s04$B@lX3{`H!IC-9h+~fp7m&qya8uAGYhG0{jfi~m; zVJbs1(6$gDFN7ftsA<2T=wu;voyl`F!X|Gr+5&PHWBBBHUHQpIK)C<_ delta 14 Wcmca~gz?5k#tl57o7>{fFarQJ;|7@k From df427aa66c738aaf2e42daf336db5bc72f245a47 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 27 Apr 2017 17:48:32 -0700 Subject: [PATCH 177/251] Code cleanup --- src/Qwiq.Core.Rest/LinkCollection.cs | 2 +- src/Qwiq.Core.Soap/FieldCollection.cs | 2 +- src/Qwiq.Core/Comparer.cs | 4 ++-- src/Qwiq.Core/FieldCollection.cs | 2 +- src/Qwiq.Core/FieldDefinitionCollection.cs | 2 +- src/Qwiq.Core/GlobalSuppressions.cs | Bin 25170 -> 32160 bytes src/Qwiq.Core/IFieldCollection.cs | 2 +- src/Qwiq.Core/IFieldDefinitionCollection.cs | 2 +- src/Qwiq.Core/INodeCollection.cs | 2 +- src/Qwiq.Core/IProjectCollection.cs | 2 +- src/Qwiq.Core/IReadOnlyObjectList.cs | 2 +- src/Qwiq.Core/IReadOnlyObjectWithIdList.cs | 2 +- .../IRegisteredLinkTypeCollection.cs | 2 +- src/Qwiq.Core/IWorkItemCollection.cs | 2 +- src/Qwiq.Core/IWorkItemLinkTypeCollection.cs | 2 +- .../IWorkItemLinkTypeEndCollection.cs | 2 +- src/Qwiq.Core/IWorkItemTypeCollection.cs | 2 +- src/Qwiq.Core/IdentityFieldValue.cs | 4 ++-- src/Qwiq.Core/NodeCollection.cs | 2 +- src/Qwiq.Core/ProjectCollection.cs | 2 +- src/Qwiq.Core/Qwiq.Core.csproj | 2 +- src/Qwiq.Core/ReadOnlyCollectionWithId.cs | 14 +++++++------- .../ReadOnlyCollectionWithIdComparer.cs | 6 +++--- ...cs => ReadOnlyObjectWithNameCollection.cs} | 12 ++++++------ src/Qwiq.Core/RegisteredLinkTypeCollection.cs | 2 +- src/Qwiq.Core/TeamFoundationIdentity.cs | 2 +- src/Qwiq.Core/WorkItem.cs | 8 -------- src/Qwiq.Core/WorkItemCollection.cs | 2 +- src/Qwiq.Core/WorkItemLinkTypeCollection.cs | 2 +- .../WorkItemLinkTypeEndCollection.cs | 2 +- src/Qwiq.Core/WorkItemTypeCollection.cs | 2 +- .../Qwiq.Core.Tests/Mocks/ExceptionThrower.cs | 2 +- 32 files changed, 45 insertions(+), 53 deletions(-) rename src/Qwiq.Core/{ReadOnlyCollection.cs => ReadOnlyObjectWithNameCollection.cs} (91%) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index dc1555ce..d5087124 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Client.Rest { - internal class LinkCollection : ReadOnlyCollection, ICollection + internal class LinkCollection : ReadOnlyObjectWithNameCollection, ICollection { public LinkCollection(IEnumerable relations, Func linkFunc) { diff --git a/src/Qwiq.Core.Soap/FieldCollection.cs b/src/Qwiq.Core.Soap/FieldCollection.cs index 1e454200..d9261fa6 100644 --- a/src/Qwiq.Core.Soap/FieldCollection.cs +++ b/src/Qwiq.Core.Soap/FieldCollection.cs @@ -123,7 +123,7 @@ public IField this[int index] } } - public bool Equals(IReadOnlyObjectWithIdList other) + public bool Equals(IReadOnlyObjectWithIdCollection other) { return Comparer.FieldCollection.Equals(this, other); } diff --git a/src/Qwiq.Core/Comparer.cs b/src/Qwiq.Core/Comparer.cs index 02ed8436..e739aa05 100644 --- a/src/Qwiq.Core/Comparer.cs +++ b/src/Qwiq.Core/Comparer.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq { public static class Comparer { - public static IEqualityComparer> FieldCollection { get; } = + public static IEqualityComparer> FieldCollection { get; } = ReadOnlyCollectionWithIdComparer.Default; public static IEqualityComparer FieldDefinition { get; } = FieldDefinitionComparer.Default; @@ -19,7 +19,7 @@ public static class Comparer public static IEqualityComparer Node { get; } = NodeComparer.Default; - public static IEqualityComparer> NodeCollection { get; } = + public static IEqualityComparer> NodeCollection { get; } = ReadOnlyCollectionWithIdComparer.Default; public static IEqualityComparer> NullableIdentity { get; } = NullableIdentifiableComparer.Default; diff --git a/src/Qwiq.Core/FieldCollection.cs b/src/Qwiq.Core/FieldCollection.cs index 716981bb..a6963042 100644 --- a/src/Qwiq.Core/FieldCollection.cs +++ b/src/Qwiq.Core/FieldCollection.cs @@ -72,7 +72,7 @@ public bool Contains(string name) } [DebuggerStepThrough] - public bool Equals(IReadOnlyObjectWithIdList other) + public bool Equals(IReadOnlyObjectWithIdCollection other) { return Comparer.FieldCollection.Equals(this, other); } diff --git a/src/Qwiq.Core/FieldDefinitionCollection.cs b/src/Qwiq.Core/FieldDefinitionCollection.cs index 32145505..afdf0b64 100644 --- a/src/Qwiq.Core/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core/FieldDefinitionCollection.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public abstract class FieldDefinitionCollection : ReadOnlyCollectionWithId, IFieldDefinitionCollection + public abstract class FieldDefinitionCollection : ReadOnlyObjectWithIdCollection, IFieldDefinitionCollection { protected internal FieldDefinitionCollection(IEnumerable fieldDefinitions) : base(fieldDefinitions, definition => definition.Name) diff --git a/src/Qwiq.Core/GlobalSuppressions.cs b/src/Qwiq.Core/GlobalSuppressions.cs index 357a4120a409d083f585508613da6fcf6965ce9b..4edb1a4fe6337a226b9cf5420c556892fb1c0a47 100644 GIT binary patch delta 777 zcmbVIO-oc^7(F_OSUUG2MkNk1D9ng`TqSIfm>je(DjRULfl8drWV9KFJ7Xz==%ydg zSrr5tEo#*;O;np!u3R}G^bcCK5!$usycaGENDEuM?{nVg<2>g*-*+0e{xp=IPqTIM z9p4OLy@%y;pVG&qS<+F$L?7Z zLQhrobG7@Q{+EkE+qOorgaTIU_+IjNOOy*ETdbzeusu=r-&RU}1?^5sgDY*J#CyMH zxaxb&`$havs>!3EmH5m(d!NCHUwk#*dD%fsX52ttc9cY(*PT@?X<0(6=CO>Nu*qM+ z3TA~1UeA)5;b!A$ezB%bnwpt$mCeYsTVly$kso@`@>@E{a=J^WmFrHGh0(J1_cC_D z=K8g_+&j_b5DTizqQryfYhS^@(G(Q_so@A(+Um3* tUa!XZ6_~+*{Osm#)%YxHkPG@E6k_+-d*- delta 14 WcmZ4RoAJ^S#tm(8o3B)!VFmy@g9n!Y diff --git a/src/Qwiq.Core/IFieldCollection.cs b/src/Qwiq.Core/IFieldCollection.cs index 7c8c1698..b880da35 100644 --- a/src/Qwiq.Core/IFieldCollection.cs +++ b/src/Qwiq.Core/IFieldCollection.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public interface IFieldCollection : IReadOnlyObjectWithIdList + public interface IFieldCollection : IReadOnlyObjectWithIdCollection { } } diff --git a/src/Qwiq.Core/IFieldDefinitionCollection.cs b/src/Qwiq.Core/IFieldDefinitionCollection.cs index 2c3a6244..12ba479c 100644 --- a/src/Qwiq.Core/IFieldDefinitionCollection.cs +++ b/src/Qwiq.Core/IFieldDefinitionCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IFieldDefinitionCollection : IReadOnlyObjectWithIdList, + public interface IFieldDefinitionCollection : IReadOnlyObjectWithIdCollection, IEquatable { } diff --git a/src/Qwiq.Core/INodeCollection.cs b/src/Qwiq.Core/INodeCollection.cs index f2e49d84..7c466e2b 100644 --- a/src/Qwiq.Core/INodeCollection.cs +++ b/src/Qwiq.Core/INodeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface INodeCollection : IReadOnlyObjectWithIdList, IEquatable + public interface INodeCollection : IReadOnlyObjectWithIdCollection, IEquatable { } } \ No newline at end of file diff --git a/src/Qwiq.Core/IProjectCollection.cs b/src/Qwiq.Core/IProjectCollection.cs index 15af35a6..9ef38bb7 100644 --- a/src/Qwiq.Core/IProjectCollection.cs +++ b/src/Qwiq.Core/IProjectCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IProjectCollection : IReadOnlyObjectList + public interface IProjectCollection : IReadOnlyObjectWithNameCollection { IProject this[Guid id] { get; } } diff --git a/src/Qwiq.Core/IReadOnlyObjectList.cs b/src/Qwiq.Core/IReadOnlyObjectList.cs index 967c6d58..3e6bc1ae 100644 --- a/src/Qwiq.Core/IReadOnlyObjectList.cs +++ b/src/Qwiq.Core/IReadOnlyObjectList.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq /// Represents a read-only collection of elements that can be accessed by index or name. ///
/// The type of elements in the read-only list. - public interface IReadOnlyObjectList : IReadOnlyList + public interface IReadOnlyObjectWithNameCollection : IReadOnlyList { /// /// Gets the element of the specified name in the read-only list. diff --git a/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs b/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs index cba740bc..d74ca22d 100644 --- a/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs +++ b/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq /// /// The type of elements in the read-only list. /// The type of the identifier of elements in the read-only list. - public interface IReadOnlyObjectWithIdList : IReadOnlyObjectList, IEquatable> + public interface IReadOnlyObjectWithIdCollection : IReadOnlyObjectWithNameCollection, IEquatable> where T : IIdentifiable { /// diff --git a/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs b/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs index c27d3467..e60564d9 100644 --- a/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/IRegisteredLinkTypeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IRegisteredLinkTypeCollection : IReadOnlyObjectList, IEquatable + public interface IRegisteredLinkTypeCollection : IReadOnlyObjectWithNameCollection, IEquatable { } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemCollection.cs b/src/Qwiq.Core/IWorkItemCollection.cs index 295682ba..0355b9c8 100644 --- a/src/Qwiq.Core/IWorkItemCollection.cs +++ b/src/Qwiq.Core/IWorkItemCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IWorkItemCollection : IReadOnlyObjectWithIdList, IEquatable + public interface IWorkItemCollection : IReadOnlyObjectWithIdCollection, IEquatable { } diff --git a/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs b/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs index e6a22f1b..2db83bb0 100644 --- a/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/IWorkItemLinkTypeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IWorkItemLinkTypeCollection : IReadOnlyObjectList, + public interface IWorkItemLinkTypeCollection : IReadOnlyObjectWithNameCollection, IEquatable { IWorkItemLinkTypeEndCollection LinkTypeEnds { get; } diff --git a/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs index 02c27102..bdb3e60c 100644 --- a/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public interface IWorkItemLinkTypeEndCollection : IReadOnlyObjectWithIdList + public interface IWorkItemLinkTypeEndCollection : IReadOnlyObjectWithIdCollection { } diff --git a/src/Qwiq.Core/IWorkItemTypeCollection.cs b/src/Qwiq.Core/IWorkItemTypeCollection.cs index 4ff48006..3655d40c 100644 --- a/src/Qwiq.Core/IWorkItemTypeCollection.cs +++ b/src/Qwiq.Core/IWorkItemTypeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public interface IWorkItemTypeCollection : IReadOnlyObjectList, IEquatable + public interface IWorkItemTypeCollection : IReadOnlyObjectWithNameCollection, IEquatable { } } \ No newline at end of file diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index fba2d10b..12fded67 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -122,8 +122,8 @@ public IdentityFieldValue(string displayName) /// Gets the the User principal name (UPN) or the down-level login name. /// /// - /// This can be in the UPN format (e.g. UserName@Example.Microsoft.com) or the down-level logon name format (e.g. - /// EXAMPLE\UserName). + /// This can be in the UPN format (e.g. user@domain.com) or the down-level logon name format (e.g. + /// domain\user). /// public string AccountName { get; } diff --git a/src/Qwiq.Core/NodeCollection.cs b/src/Qwiq.Core/NodeCollection.cs index 6a9439fe..6d363ff1 100644 --- a/src/Qwiq.Core/NodeCollection.cs +++ b/src/Qwiq.Core/NodeCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class NodeCollection : ReadOnlyCollectionWithId, INodeCollection + public class NodeCollection : ReadOnlyObjectWithIdCollection, INodeCollection { internal NodeCollection(params INode[] nodes) : this(nodes as IEnumerable) diff --git a/src/Qwiq.Core/ProjectCollection.cs b/src/Qwiq.Core/ProjectCollection.cs index 98c4d37e..6e9cc9b8 100644 --- a/src/Qwiq.Core/ProjectCollection.cs +++ b/src/Qwiq.Core/ProjectCollection.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - internal class ProjectCollection : ReadOnlyCollectionWithId, IProjectCollection + internal class ProjectCollection : ReadOnlyObjectWithIdCollection, IProjectCollection { internal ProjectCollection(params IProject[] projects) : this(projects as IEnumerable) diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 5776dd73..5f3185f5 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -172,9 +172,9 @@ - + diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs index b8d6f023..6f9afef5 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs @@ -3,25 +3,25 @@ namespace Microsoft.Qwiq { - public abstract class ReadOnlyCollectionWithId : ReadOnlyCollection, IReadOnlyObjectWithIdList + public abstract class ReadOnlyObjectWithIdCollection : ReadOnlyObjectWithNameCollection, IReadOnlyObjectWithIdCollection where T : IIdentifiable { private readonly Func _idFunc; private readonly IDictionary _mapById; - protected ReadOnlyCollectionWithId(IEnumerable items, Func nameFunc) + protected ReadOnlyObjectWithIdCollection(IEnumerable items, Func nameFunc) :this(items, nameFunc, arg => arg.Id) { } - protected ReadOnlyCollectionWithId(IEnumerable items, Func nameFunc, Func idFunc) + protected ReadOnlyObjectWithIdCollection(IEnumerable items, Func nameFunc, Func idFunc) :base(items, nameFunc) { _idFunc = idFunc ?? throw new ArgumentNullException(nameof(idFunc)); _mapById = new Dictionary(); } - protected ReadOnlyCollectionWithId(IEnumerable items) + protected ReadOnlyObjectWithIdCollection(IEnumerable items) :base(items) { _idFunc = a => a.Id; @@ -30,7 +30,7 @@ protected ReadOnlyCollectionWithId(IEnumerable items) public virtual bool Contains(TId id) { - Ensure(); + base.Ensure(); return _mapById.ContainsKey(id); } @@ -72,14 +72,14 @@ protected void AddById(TId id, int index) } } - public virtual bool Equals(IReadOnlyObjectWithIdList other) + public virtual bool Equals(IReadOnlyObjectWithIdCollection other) { return ReadOnlyCollectionWithIdComparer.Default.Equals(this, other); } public override bool Equals(object obj) { - return ReadOnlyCollectionWithIdComparer.Default.Equals(this, obj as IReadOnlyObjectWithIdList); + return ReadOnlyCollectionWithIdComparer.Default.Equals(this, obj as IReadOnlyObjectWithIdCollection); } public override int GetHashCode() diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs b/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs index 6b8cca35..7891b638 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs @@ -2,12 +2,12 @@ namespace Microsoft.Qwiq { - public class ReadOnlyCollectionWithIdComparer : GenericComparer> + public class ReadOnlyCollectionWithIdComparer : GenericComparer> where T : IIdentifiable { public new static readonly ReadOnlyCollectionWithIdComparer Default = new ReadOnlyCollectionWithIdComparer(); - public override bool Equals(IReadOnlyObjectWithIdList x, IReadOnlyObjectWithIdList y) + public override bool Equals(IReadOnlyObjectWithIdCollection x, IReadOnlyObjectWithIdCollection y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; @@ -31,7 +31,7 @@ public override bool Equals(IReadOnlyObjectWithIdList x, IReadOnlyObject return true; } - public override int GetHashCode(IReadOnlyObjectWithIdList obj) + public override int GetHashCode(IReadOnlyObjectWithIdCollection obj) { if (ReferenceEquals(obj, null)) return 0; diff --git a/src/Qwiq.Core/ReadOnlyCollection.cs b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs similarity index 91% rename from src/Qwiq.Core/ReadOnlyCollection.cs rename to src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs index 73ad42dd..901b5f01 100644 --- a/src/Qwiq.Core/ReadOnlyCollection.cs +++ b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs @@ -10,7 +10,7 @@ namespace Microsoft.Qwiq /// Base class for common operations for Collections. ///
/// - public abstract class ReadOnlyCollection : IReadOnlyObjectList + public abstract class ReadOnlyObjectWithNameCollection : IReadOnlyObjectWithNameCollection { private readonly object _lockObj = new object(); @@ -24,24 +24,24 @@ public abstract class ReadOnlyCollection : IReadOnlyObjectList private IDictionary _mapByName; - protected ReadOnlyCollection(Func> itemFactory, Func nameFunc) + protected ReadOnlyObjectWithNameCollection(Func> itemFactory, Func nameFunc) { ItemFactory = itemFactory ?? throw new ArgumentNullException(nameof(itemFactory)); _nameFunc = nameFunc ?? throw new ArgumentNullException(nameof(nameFunc)); } - protected ReadOnlyCollection(IEnumerable items, Func nameFunc) + protected ReadOnlyObjectWithNameCollection(IEnumerable items, Func nameFunc) { ItemFactory = () => items ?? Enumerable.Empty(); _nameFunc = nameFunc; } - protected ReadOnlyCollection(IEnumerable items) + protected ReadOnlyObjectWithNameCollection(IEnumerable items) : this(items, null) { } - protected ReadOnlyCollection() + protected ReadOnlyObjectWithNameCollection() { Initialize(); } @@ -139,7 +139,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - T IReadOnlyObjectList.GetItem(int index) + T IReadOnlyObjectWithNameCollection.GetItem(int index) { return GetItem(index); } diff --git a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs index 89920a09..9922939e 100644 --- a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public class RegisteredLinkTypeCollection : ReadOnlyCollection, IRegisteredLinkTypeCollection + public class RegisteredLinkTypeCollection : ReadOnlyObjectWithNameCollection, IRegisteredLinkTypeCollection { public RegisteredLinkTypeCollection(IEnumerable linkTypes) : base(linkTypes, type => type.Name) diff --git a/src/Qwiq.Core/TeamFoundationIdentity.cs b/src/Qwiq.Core/TeamFoundationIdentity.cs index b61da1fe..c489568b 100644 --- a/src/Qwiq.Core/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core/TeamFoundationIdentity.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq public abstract class TeamFoundationIdentity : ITeamFoundationIdentity, IEquatable { private string _uniqueName; - protected static readonly IIdentityDescriptor[] ZeroLengthArrayOfIdentityDescriptor = new IIdentityDescriptor[0]; + protected internal static readonly IIdentityDescriptor[] ZeroLengthArrayOfIdentityDescriptor = new IIdentityDescriptor[0]; private TeamFoundationIdentity() { diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index fbbb11f1..b2b4dbed 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -52,9 +52,6 @@ public bool Equals(IWorkItem other) public virtual IFieldCollection Fields => _fields == null ? throw new NotSupportedException() : _fields.Value; - public int Index => -2; - - public new virtual int HyperlinkCount => base.HyperlinkCount.GetValueOrDefault(0); public new virtual int Id => base.Id.GetValueOrDefault(0); @@ -114,11 +111,6 @@ public override object this[string name] } } - public string GetTagLine() - { - throw new NotSupportedException(); - } - public virtual void ApplyRules(bool doNotUpdateChangedBy = false) { throw new NotSupportedException(); diff --git a/src/Qwiq.Core/WorkItemCollection.cs b/src/Qwiq.Core/WorkItemCollection.cs index 301094d1..b54ee15a 100644 --- a/src/Qwiq.Core/WorkItemCollection.cs +++ b/src/Qwiq.Core/WorkItemCollection.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class WorkItemCollection : ReadOnlyCollectionWithId, IWorkItemCollection + public class WorkItemCollection : ReadOnlyObjectWithIdCollection, IWorkItemCollection { public WorkItemCollection(IEnumerable workItems) :base(workItems) diff --git a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs index aae99977..ca0a59ea 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeCollection : ReadOnlyCollection, IWorkItemLinkTypeCollection + public class WorkItemLinkTypeCollection : ReadOnlyObjectWithNameCollection, IWorkItemLinkTypeCollection { private readonly Lazy _ltCol; diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs index 6db886b5..03eec353 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeEndCollection : ReadOnlyCollectionWithId, + public class WorkItemLinkTypeEndCollection : ReadOnlyObjectWithIdCollection, IWorkItemLinkTypeEndCollection { internal WorkItemLinkTypeEndCollection(IEnumerable linkTypes) diff --git a/src/Qwiq.Core/WorkItemTypeCollection.cs b/src/Qwiq.Core/WorkItemTypeCollection.cs index 39c29a80..08285ecb 100644 --- a/src/Qwiq.Core/WorkItemTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemTypeCollection.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - public class WorkItemTypeCollection : ReadOnlyCollection, IWorkItemTypeCollection + public class WorkItemTypeCollection : ReadOnlyObjectWithNameCollection, IWorkItemTypeCollection { [DebuggerStepThrough] internal WorkItemTypeCollection(Func> workItemTypesFactory) diff --git a/test/Qwiq.Core.Tests/Mocks/ExceptionThrower.cs b/test/Qwiq.Core.Tests/Mocks/ExceptionThrower.cs index c81e3834..fdc82fa0 100644 --- a/test/Qwiq.Core.Tests/Mocks/ExceptionThrower.cs +++ b/test/Qwiq.Core.Tests/Mocks/ExceptionThrower.cs @@ -13,7 +13,7 @@ public ExceptionThrower(string argumentName) public void ThrowException() { - throw new ArgumentException(null, _argumentName); ; + throw new ArgumentException(null, _argumentName); } } From 77ff02ab8730c20df3f38094462ee3f3ef5ced06 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 27 Apr 2017 19:56:49 -0700 Subject: [PATCH 178/251] Add ReSharper annotations and Code Contracts Use a combination of code contracts and R# annotations: - Code contracts may not be able to express all assumptions made during evaluation of path usage (e.g. that a delegate is consumed immediately and R# should not throw "access to a modified closure" warning) - Code contracts provides static and runtime checking regarding expected input and output behaviors (a "living" documentation). This permits easier and more prevalent assertions to intercept unexpected behavior before going too far down the stack in a bad state. --- src/Qwiq.Core.Rest/Extensions.cs | 38 +- src/Qwiq.Core.Rest/FieldDefinition.cs | 7 +- src/Qwiq.Core.Rest/IdentityDescriptor.cs | 8 +- src/Qwiq.Core.Rest/Node.cs | 12 +- src/Qwiq.Core.Rest/Project.cs | 4 +- src/Qwiq.Core.Rest/Query.cs | 15 +- src/Qwiq.Core.Rest/TeamFoundationIdentity.cs | 9 +- .../Credentials/AuthenticationOptions.cs | 18 +- .../ExceptionHandlingDynamicProxy.cs | 21 +- .../ExceptionHandlingDynamicProxyFactory.cs | 51 +- src/Qwiq.Core/Exceptions/ExceptionMapper.cs | 12 +- src/Qwiq.Core/FieldDefinition.cs | 13 +- src/Qwiq.Core/FieldDefinitionComparer.cs | 9 +- src/Qwiq.Core/GenericComparer.cs | 8 +- src/Qwiq.Core/Hyperlink.cs | 11 +- src/Qwiq.Core/IReadOnlyObjectWithIdList.cs | 8 +- src/Qwiq.Core/IRevisionInternal.cs | 11 +- src/Qwiq.Core/ITeamFoundationIdentity.cs | 84 ++ src/Qwiq.Core/IWorkItemType.Extensions.cs | 21 +- src/Qwiq.Core/IdentityFieldValue.cs | 59 +- src/Qwiq.Core/Link.cs | 7 +- src/Qwiq.Core/NodeComparer.cs | 4 +- src/Qwiq.Core/Qwiq.Core.csproj | 4 + src/Qwiq.Core/ReadOnlyCollectionWithId.cs | 67 +- .../ReadOnlyObjectWithNameCollection.cs | 13 +- src/Qwiq.Core/RevisionInternalContract.cs | 20 + src/Qwiq.Core/WorkItem.cs | 11 +- src/Qwiq.Core/WorkItemCollection.cs | 6 +- src/Qwiq.Core/WorkItemCommon.cs | 1 - src/Qwiq.Core/WorkItemCopyFlags.cs | 4 + src/Qwiq.Core/WorkItemCore.cs | 5 +- src/Qwiq.Core/WorkItemType.cs | 16 +- src/Qwiq.Core/WorkItemTypeComparer.cs | 4 +- .../DisplayNameToAliasValueConverter.cs | 9 +- src/Qwiq.Identity/IIdentityValueConverter.cs | 5 +- .../IdentityAliasValueConverter.cs | 20 +- src/Qwiq.Identity/Qwiq.Identity.csproj | 3 + src/Qwiq.Linq/CachingFieldMapper.cs | 15 +- src/Qwiq.Linq/FieldMapperContract.cs | 32 + src/Qwiq.Linq/IFieldMapper.cs | 10 +- src/Qwiq.Linq/Projector.cs | 13 +- src/Qwiq.Linq/Query.cs | 21 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 4 + ...ulkIdentityAwareAttributeMapperStrategy.cs | 4 + .../IdentityFieldAttributeVisitor.cs | 3 + src/Qwiq.Mapper/FieldMapper.cs | 11 +- src/Qwiq.Mapper/IWorkItemMapper.cs | 10 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 4 + src/Qwiq.Mapper/WorkItemMapper.cs | 15 +- src/Qwiq.Mapper/WorkItemMapperContract.cs | 40 + src/ReSharper.Annotations.cs | 1272 +++++++++++++++++ .../Identity/IdentityMapperTests.cs | 2 +- test/Qwiq.Mocks/CoreFieldDefinitions.cs | 32 +- .../Qwiq.Tests.Common.csproj | 3 + 54 files changed, 1922 insertions(+), 187 deletions(-) create mode 100644 src/Qwiq.Core/RevisionInternalContract.cs create mode 100644 src/Qwiq.Linq/FieldMapperContract.cs create mode 100644 src/Qwiq.Mapper/WorkItemMapperContract.cs create mode 100644 src/ReSharper.Annotations.cs diff --git a/src/Qwiq.Core.Rest/Extensions.cs b/src/Qwiq.Core.Rest/Extensions.cs index 45bdc03d..fc6532bd 100644 --- a/src/Qwiq.Core.Rest/Extensions.cs +++ b/src/Qwiq.Core.Rest/Extensions.cs @@ -1,36 +1,44 @@ -using Microsoft.Qwiq.Exceptions; +using JetBrains.Annotations; + +using Microsoft.Qwiq.Exceptions; using Microsoft.VisualStudio.Services.WebApi; namespace Microsoft.Qwiq.Client.Rest { internal static class Extensions { - internal static IWorkItem AsProxy(this WorkItem item) + [CanBeNull] + [Pure] + [ContractAnnotation("null => null; notnull => notnull")] + internal static IWorkItem AsProxy([CanBeNull] this WorkItem item) { - return item == null - ? null - : ExceptionHandlingDynamicProxyFactory.Create(item); + return item == null ? null : ExceptionHandlingDynamicProxyFactory.Create(item); } - internal static IQuery AsProxy(this Query query) + [CanBeNull] + [Pure] + [ContractAnnotation("null => null; notnull => notnull")] + internal static IQuery AsProxy([CanBeNull] this Query query) { - return query == null - ? null - : ExceptionHandlingDynamicProxyFactory.Create(query); + return query == null ? null : ExceptionHandlingDynamicProxyFactory.Create(query); } - internal static IIdentityDescriptor AsProxy(this VisualStudio.Services.Identity.IdentityDescriptor value) + [CanBeNull] + [Pure] + [ContractAnnotation("null => null; notnull => notnull")] + internal static IIdentityDescriptor AsProxy([CanBeNull] this VisualStudio.Services.Identity.IdentityDescriptor value) { - return value == null - ? null - : ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptor(value)); + return value == null ? null : ExceptionHandlingDynamicProxyFactory.Create(new IdentityDescriptor(value)); } - internal static IInternalTeamProjectCollection AsProxy(this VssConnection tfsNative) + [CanBeNull] + [Pure] + [ContractAnnotation("null => null; notnull => notnull")] + internal static IInternalTeamProjectCollection AsProxy([CanBeNull] this VssConnection tfsNative) { return tfsNative == null ? null : ExceptionHandlingDynamicProxyFactory.Create(new VssConnectionAdapter(tfsNative)); } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/FieldDefinition.cs b/src/Qwiq.Core.Rest/FieldDefinition.cs index a8e3e9a1..eb87a43a 100644 --- a/src/Qwiq.Core.Rest/FieldDefinition.cs +++ b/src/Qwiq.Core.Rest/FieldDefinition.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; @@ -6,9 +9,11 @@ namespace Microsoft.Qwiq.Client.Rest { internal class FieldDefinition : Qwiq.FieldDefinition { - internal FieldDefinition(WorkItemFieldReference field) + internal FieldDefinition([NotNull] WorkItemFieldReference field) :base(field.ReferenceName, field.Name) { + Contract.Requires(field != null); + if (field == null) throw new ArgumentNullException(nameof(field)); } } diff --git a/src/Qwiq.Core.Rest/IdentityDescriptor.cs b/src/Qwiq.Core.Rest/IdentityDescriptor.cs index 67c7692b..c851d03a 100644 --- a/src/Qwiq.Core.Rest/IdentityDescriptor.cs +++ b/src/Qwiq.Core.Rest/IdentityDescriptor.cs @@ -1,14 +1,18 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq.Client.Rest { public class IdentityDescriptor : Qwiq.IdentityDescriptor { - internal IdentityDescriptor(VisualStudio.Services.Identity.IdentityDescriptor descriptor) + internal IdentityDescriptor([NotNull] VisualStudio.Services.Identity.IdentityDescriptor descriptor) : base(descriptor.IdentityType, descriptor.Identifier) { + Contract.Requires(descriptor != null); + if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/Node.cs b/src/Qwiq.Core.Rest/Node.cs index 56f375a4..a774ef30 100644 --- a/src/Qwiq.Core.Rest/Node.cs +++ b/src/Qwiq.Core.Rest/Node.cs @@ -1,13 +1,21 @@ using System; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Client.Rest { internal class Node : Qwiq.Node { - internal Node(WorkItemClassificationNode node, INode parentNode = null) + internal Node([NotNull] WorkItemClassificationNode node) + : this(node, null) + { + } + + internal Node([NotNull] WorkItemClassificationNode node, [CanBeNull] INode parentNode) : base( node.Id, node.StructureType == TreeNodeStructureType.Area, @@ -17,6 +25,8 @@ internal Node(WorkItemClassificationNode node, INode parentNode = null) () => parentNode, n => node.Children?.Any() ?? false ? node.Children.Select(s => new Node(s, n)).ToList() : Enumerable.Empty()) { + Contract.Requires(node != null); + if (node == null) throw new ArgumentNullException(nameof(node)); } } diff --git a/src/Qwiq.Core.Rest/Project.cs b/src/Qwiq.Core.Rest/Project.cs index 369e0d3d..6b8c8c27 100644 --- a/src/Qwiq.Core.Rest/Project.cs +++ b/src/Qwiq.Core.Rest/Project.cs @@ -1,6 +1,8 @@ using System; using System.Linq; +using JetBrains.Annotations; + using Microsoft.TeamFoundation.Core.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; @@ -8,7 +10,7 @@ namespace Microsoft.Qwiq.Client.Rest { internal class Project : Qwiq.Project { - internal Project(TeamProjectReference project, WorkItemStore store) + internal Project([NotNull] TeamProjectReference project, [NotNull] WorkItemStore store) : base( project.Id, project.Name, diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 56e1c985..1c908fde 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using JetBrains.Annotations; + using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Client.Rest @@ -31,15 +34,19 @@ internal class Query : IQuery private IEnumerable _ids; - internal Query(IEnumerable ids, Wiql query, WorkItemStore workItemStore) + internal Query(IEnumerable ids, Wiql query, [NotNull] WorkItemStore workItemStore) : this(query, false, workItemStore) { + Contract.Requires(workItemStore != null); + _ids = ids; } - internal Query(Wiql query, bool timePrecision, WorkItemStore workItemStore) + internal Query(Wiql query, bool timePrecision, [NotNull] WorkItemStore workItemStore) { + Contract.Requires(workItemStore != null); + _workItemStore = workItemStore ?? throw new ArgumentNullException(nameof(workItemStore)); _timePrecision = timePrecision; _query = query; @@ -58,6 +65,7 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() return wit.LinkTypeEnds; } + [ItemNotNull] public IEnumerable RunLinkQuery() { // Eager loading for the link type ID (which is not returned by the REST API) causes ~250ms delay @@ -85,8 +93,11 @@ public IWorkItemCollection RunQuery() return new WorkItemCollection(RunQueryImpl().ToList()); } + [NotNull] private IEnumerable RunQueryImpl() { + Contract.Ensures(Contract.Result>() != null); + if (_ids == null && _query != null) { var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); diff --git a/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs index a02a9d31..18762a1f 100644 --- a/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core.Rest/TeamFoundationIdentity.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + using Microsoft.VisualStudio.Services.Identity; namespace Microsoft.Qwiq.Client.Rest @@ -16,9 +19,11 @@ internal class TeamFoundationIdentity : Qwiq.TeamFoundationIdentity private readonly Lazy> _members; - internal TeamFoundationIdentity(Identity identity) + internal TeamFoundationIdentity([NotNull] Identity identity) : base(identity.IsActive, identity.Id, identity.UniqueUserId) { + Contract.Requires(identity != null); + _identity = identity ?? throw new ArgumentNullException(nameof(identity)); DisplayName = identity.DisplayName; @@ -28,7 +33,7 @@ internal TeamFoundationIdentity(Identity identity) _memberOf = new Lazy>(() => identity.MemberOf.Select(item => item.AsProxy())); _members = new Lazy>(() => identity.Members.Select(item => item.AsProxy())); } - + public override IIdentityDescriptor Descriptor => _descriptor.Value; public override string DisplayName { get; } diff --git a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs index d69ec47f..f44f6560 100644 --- a/src/Qwiq.Core/Credentials/AuthenticationOptions.cs +++ b/src/Qwiq.Core/Credentials/AuthenticationOptions.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Microsoft.VisualStudio.Services.Client; using Microsoft.VisualStudio.Services.Common; using System; @@ -6,7 +7,7 @@ namespace Microsoft.Qwiq.Credentials { /// - /// Represents options for authentication against a Team Foundation Server. + /// Represents options for authentication against a Team Foundation Server. /// public class AuthenticationOptions { @@ -16,6 +17,7 @@ public class AuthenticationOptions /// Initializes a new instance of the class. /// /// The URI of the Team Foundation Server, including the project collection. + [PublicAPI] public AuthenticationOptions(string uri) : this(uri, AuthenticationTypes.All) { @@ -26,6 +28,7 @@ public AuthenticationOptions(string uri) /// /// The URI of the Team Foundation Server, including the project collection. /// The authentication types to use against the server. + [PublicAPI] public AuthenticationOptions(string uri, AuthenticationTypes authenticationTypes) : this(new Uri(uri, UriKind.Absolute), authenticationTypes) { @@ -35,6 +38,7 @@ public AuthenticationOptions(string uri, AuthenticationTypes authenticationTypes /// Initializes a new instance of the class. /// /// The URI of the Team Foundation Server, including the project collection. + [PublicAPI] public AuthenticationOptions(Uri uri) : this(uri, AuthenticationTypes.All) { @@ -45,6 +49,7 @@ public AuthenticationOptions(Uri uri) /// /// The URI of the Team Foundation Server, including the project collection. /// The authentication types. + [PublicAPI] public AuthenticationOptions(Uri uri, AuthenticationTypes authenticationTypes) : this(uri, authenticationTypes, null) { @@ -57,6 +62,7 @@ public AuthenticationOptions(Uri uri, AuthenticationTypes authenticationTypes) /// The authentication types. /// The credentials factory. /// uri + [PublicAPI] public AuthenticationOptions( Uri uri, AuthenticationTypes authenticationTypes, @@ -68,8 +74,10 @@ public AuthenticationOptions( _createCredentials = credentialsFactory ?? CredentialsFactory; } + [PublicAPI] public AuthenticationTypes AuthenticationTypes { get; } + [CanBeNull] public IEnumerable Credentials { get @@ -82,8 +90,10 @@ public IEnumerable Credentials } } + [CanBeNull] public CredentialsNotifications Notifications { get; set; } + [NotNull] public Uri Uri { get; } private static IEnumerable CredentialsFactory(AuthenticationTypes t) @@ -112,11 +122,7 @@ private static IEnumerable CredentialsFactory(AuthenticationType }; // Use the Windows identity of the logged on user - yield return new VssClientCredentials(true) - { - Storage = storage, - PromptType = CredentialPromptType.PromptIfNeeded - }; + yield return new VssClientCredentials(true) { Storage = storage, PromptType = CredentialPromptType.PromptIfNeeded }; } } diff --git a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs index a61c9519..cd761be8 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs @@ -1,22 +1,28 @@ using System; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Runtime.ExceptionServices; using Castle.DynamicProxy; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Exceptions { [DebuggerStepThrough] - public class ExceptionHandlingDynamicProxy : IInterceptor + public class ExceptionHandlingDynamicProxy : IInterceptor { + [NotNull] private readonly IExceptionMapper _exceptionMapper; - public ExceptionHandlingDynamicProxy(IExceptionMapper exceptionMapper) + public ExceptionHandlingDynamicProxy([NotNull] IExceptionMapper exceptionMapper) { + Contract.Requires(exceptionMapper != null); + _exceptionMapper = exceptionMapper; } - public void Intercept(IInvocation invocation) + public void Intercept([NotNull] IInvocation invocation) { try { @@ -28,6 +34,11 @@ public void Intercept(IInvocation invocation) ExceptionDispatchInfo.Capture(_exceptionMapper.Map(e)).Throw(); } } - } -} + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_exceptionMapper != null); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs index 0e040095..ca4ef630 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs @@ -1,39 +1,46 @@ -using System.Collections.Generic; using Castle.DynamicProxy; +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Diagnostics.Contracts; namespace Microsoft.Qwiq.Exceptions { public static class ExceptionHandlingDynamicProxyFactory { private static readonly ProxyGenerator Generator = new ProxyGenerator(); - private static readonly ProxyGenerationOptions Options = new ProxyGenerationOptions { BaseTypeForInterfaceProxy = typeof(ProxyBase) }; - public static T Create(T instance) where T : class + private static readonly ProxyGenerationOptions Options = + new ProxyGenerationOptions { BaseTypeForInterfaceProxy = typeof(ProxyBase) }; + + [JetBrains.Annotations.Pure] + [NotNull] + public static T Create([NotNull] T instance) + where T : class { + Contract.Requires(instance != null); + Contract.Ensures(Contract.Result() != null); + return Create( - instance, - new IExceptionExploder[] - { - new AggregateExceptionExploder(), - new InnerExceptionExploder() - }, - new IExceptionMapper[] - { - new InvalidOperationExceptionMapper(), - new TransientExceptionMapper() - }); + instance, + new IExceptionExploder[] { new AggregateExceptionExploder(), new InnerExceptionExploder() }, + new IExceptionMapper[] { new InvalidOperationExceptionMapper(), new TransientExceptionMapper() }); } - internal static T Create(T instance, IEnumerable exploders, IEnumerable mappers) where T : class + [NotNull] + [JetBrains.Annotations.Pure] + internal static T Create( + [NotNull] T instance, + [NotNull] IEnumerable exploders, + [NotNull] IEnumerable mappers) + where T : class { - var proxy = - new ExceptionHandlingDynamicProxy( - new ExceptionMapper( - exploders, - mappers)); + Contract.Requires(instance != null); + Contract.Requires(exploders != null); + Contract.Requires(mappers != null); + + var proxy = new ExceptionHandlingDynamicProxy(new ExceptionMapper(exploders, mappers)); return (T)Generator.CreateInterfaceProxyWithTarget(typeof(T), instance, Options, proxy); } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/Exceptions/ExceptionMapper.cs b/src/Qwiq.Core/Exceptions/ExceptionMapper.cs index bb4c1b99..e6f1c50c 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionMapper.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionMapper.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Exceptions { internal class ExceptionMapper : IExceptionMapper @@ -9,10 +12,13 @@ internal class ExceptionMapper : IExceptionMapper private readonly IEnumerable _exploders; private readonly IEnumerable _mappers; - public ExceptionMapper(IEnumerable exploders, IEnumerable mappers) + public ExceptionMapper([NotNull] IEnumerable exploders, [NotNull] IEnumerable mappers) { - _exploders = exploders; - _mappers = mappers; + Contract.Requires(exploders != null); + Contract.Requires(mappers != null); + + _exploders = exploders ?? throw new ArgumentNullException(nameof(exploders)); + _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); } public Exception Map(Exception ex) diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs index e8a9b24d..660239fb 100644 --- a/src/Qwiq.Core/FieldDefinition.cs +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { @@ -6,9 +9,12 @@ namespace Microsoft.Qwiq /// public class FieldDefinition : IFieldDefinition, IEquatable { - internal FieldDefinition(int id, string referenceName, string name) + internal FieldDefinition(int id, [NotNull] string referenceName, [NotNull] string name) { + Contract.Requires(!string.IsNullOrWhiteSpace(referenceName)); + Contract.Requires(!string.IsNullOrWhiteSpace(name)); + if (string.IsNullOrWhiteSpace(referenceName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); @@ -30,8 +36,11 @@ internal FieldDefinition(int id, string referenceName, string name) } } - internal FieldDefinition(string referenceName, string name) + internal FieldDefinition([NotNull] string referenceName, [NotNull] string name) { + Contract.Requires(!string.IsNullOrWhiteSpace(referenceName)); + Contract.Requires(!string.IsNullOrWhiteSpace(name)); + if (string.IsNullOrWhiteSpace(referenceName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); diff --git a/src/Qwiq.Core/FieldDefinitionComparer.cs b/src/Qwiq.Core/FieldDefinitionComparer.cs index ffaa8cd0..2b2b996f 100644 --- a/src/Qwiq.Core/FieldDefinitionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionComparer.cs @@ -1,17 +1,20 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class FieldDefinitionComparer : GenericComparer { private FieldDefinitionComparer() { - + } + [NotNull] internal new static FieldDefinitionComparer Default => Nested.Instance; - public override bool Equals(IFieldDefinition x, IFieldDefinition y) + public override bool Equals([CanBeNull] IFieldDefinition x, [CanBeNull] IFieldDefinition y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; @@ -23,7 +26,7 @@ public override bool Equals(IFieldDefinition x, IFieldDefinition y) && StringComparer.OrdinalIgnoreCase.Equals(x.ReferenceName, y.ReferenceName); } - public override int GetHashCode(IFieldDefinition obj) + public override int GetHashCode([CanBeNull] IFieldDefinition obj) { if (ReferenceEquals(obj, null)) return 0; diff --git a/src/Qwiq.Core/GenericComparer.cs b/src/Qwiq.Core/GenericComparer.cs index 7bd8c531..3c8f85aa 100644 --- a/src/Qwiq.Core/GenericComparer.cs +++ b/src/Qwiq.Core/GenericComparer.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class GenericComparer : IComparer, IEqualityComparer @@ -85,7 +87,7 @@ public virtual int Compare(T x, T y) return object.Equals(x, y) ? 0 : -1; } - public virtual bool Equals(T x, T y) + public virtual bool Equals([CanBeNull] T x, [CanBeNull] T y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; @@ -94,9 +96,9 @@ public virtual bool Equals(T x, T y) return Compare(x, y) == 0; } - public virtual int GetHashCode(T obj) + public virtual int GetHashCode([CanBeNull] T obj) { - return obj.GetHashCode(); + return obj?.GetHashCode() ?? 0; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/Hyperlink.cs b/src/Qwiq.Core/Hyperlink.cs index beb09603..e519058c 100644 --- a/src/Qwiq.Core/Hyperlink.cs +++ b/src/Qwiq.Core/Hyperlink.cs @@ -1,13 +1,18 @@ using System; using System.Diagnostics; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { public class Hyperlink : Link, IHyperlink { - internal Hyperlink(string location, string comment = null) + internal Hyperlink([NotNull] string location, [CanBeNull] string comment = null) : base(comment, BaseLinkType.Hyperlink) { + Contract.Requires(!string.IsNullOrEmpty(location)); + if (string.IsNullOrEmpty(location)) throw new ArgumentException("Value cannot be null or empty.", nameof(location)); Location = location; } @@ -16,7 +21,7 @@ internal Hyperlink(string location, string comment = null) public string Location { get; } /// - public bool Equals(IHyperlink other) + public bool Equals([CanBeNull] IHyperlink other) { if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(other, null)) return false; @@ -25,7 +30,7 @@ public bool Equals(IHyperlink other) } [DebuggerStepThrough] - public override bool Equals(object obj) + public override bool Equals([CanBeNull] object obj) { return Equals(obj as IHyperlink); } diff --git a/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs b/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs index d74ca22d..32e59c28 100644 --- a/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs +++ b/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs @@ -1,5 +1,7 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { /// @@ -15,14 +17,14 @@ public interface IReadOnlyObjectWithIdCollection : IReadOnlyObjectWithNa /// /// The identity of an element. /// true if the item is found; otherwise, false. - bool Contains(TId id); + bool Contains([NotNull] TId id); /// /// Gets the element with the specified id from the read-only collection. /// /// The identity of an element. /// The element with the specified in the read-only list. - T GetById(TId id); + T GetById([NotNull] TId id); /// /// Attempts to get the value associated with the specified name from the read-only list. @@ -33,6 +35,6 @@ public interface IReadOnlyObjectWithIdCollection : IReadOnlyObjectWithNa /// or the default value of the type if the operation failed. /// /// true if the name was found in the read-only list; otherwise, false. - bool TryGetById(TId id, out T value); + bool TryGetById([NotNull] TId id, [CanBeNull] out T value); } } \ No newline at end of file diff --git a/src/Qwiq.Core/IRevisionInternal.cs b/src/Qwiq.Core/IRevisionInternal.cs index 3bfe35d1..be4fe930 100644 --- a/src/Qwiq.Core/IRevisionInternal.cs +++ b/src/Qwiq.Core/IRevisionInternal.cs @@ -1,9 +1,16 @@ +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; + namespace Microsoft.Qwiq { + [ContractClass(typeof(RevisionInternalContract))] internal interface IRevisionInternal { - object GetCurrentFieldValue(IFieldDefinition fieldDefinition); + [JetBrains.Annotations.Pure] + [CanBeNull] + object GetCurrentFieldValue([NotNull] IFieldDefinition fieldDefinition); - void SetFieldValue(IFieldDefinition fieldDefinition, object value); + void SetFieldValue([NotNull] IFieldDefinition fieldDefinition, [CanBeNull] object value); } } \ No newline at end of file diff --git a/src/Qwiq.Core/ITeamFoundationIdentity.cs b/src/Qwiq.Core/ITeamFoundationIdentity.cs index e3fe5e06..4276b1bb 100644 --- a/src/Qwiq.Core/ITeamFoundationIdentity.cs +++ b/src/Qwiq.Core/ITeamFoundationIdentity.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { + [ContractClass(typeof(TeamFoundationIdentityContract))] public interface ITeamFoundationIdentity { /// /// Gets the unique identifier for the identity's provider. /// /// The descriptor. + [NotNull] IIdentityDescriptor Descriptor { get; } /// @@ -18,6 +23,7 @@ public interface ITeamFoundationIdentity /// /// If the identity provider does not supply a full name, and no custom display name is set, another property like account name or email address will be used as the display name. /// + [NotNull] string DisplayName { get; } /// @@ -35,12 +41,14 @@ public interface ITeamFoundationIdentity /// Gets the set of of groups containing this identity. /// /// The member of. + [NotNull] IEnumerable MemberOf { get; } /// /// Gets the set of s for members of this identity. /// /// The members. + [NotNull] IEnumerable Members { get; } /// @@ -67,6 +75,7 @@ public interface ITeamFoundationIdentity /// CONTOSO\DanJ:1 /// /// The unique name of the identity. + [NotNull] string UniqueName { get; } /// @@ -95,4 +104,79 @@ public interface ITeamFoundationIdentity /// IEnumerable> GetProperties(); } + + [ContractClassFor(typeof(ITeamFoundationIdentity))] + internal abstract class TeamFoundationIdentityContract : ITeamFoundationIdentity + { + public IIdentityDescriptor Descriptor + { + get + { + Contract.Ensures(Contract.Result() != null); + + return default(IIdentityDescriptor); + } + } + + /// + public abstract bool IsContainer { get; } + + public IEnumerable MemberOf + { + get + { + Contract.Ensures(Contract.Result>() != null); + + return default(IEnumerable); + } + } + + public IEnumerable Members + { + get + { + Contract.Ensures(Contract.Result>() != null); + + return default(IEnumerable); + } + } + + /// + public abstract Guid TeamFoundationId { get; } + + public string DisplayName + { + get + { + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result())); + + return default(string); + } + } + + /// + public abstract bool IsActive { get; } + + public string UniqueName + { + get + { + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result())); + + return default(string); + } + } + + /// + public abstract int UniqueUserId { get; } + + /// + public abstract string GetAttribute(string name, string defaultValue); + + /// + public abstract object GetProperty(string name); + + /// + public abstract IEnumerable> GetProperties(); + } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemType.Extensions.cs b/src/Qwiq.Core/IWorkItemType.Extensions.cs index ea6df1e2..b1e2ae17 100644 --- a/src/Qwiq.Core/IWorkItemType.Extensions.cs +++ b/src/Qwiq.Core/IWorkItemType.Extensions.cs @@ -1,13 +1,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public static partial class Extensions { - public static IWorkItem NewWorkItem(this IWorkItemType wit, IEnumerable> values) + [MustUseReturnValue] + [ContractAnnotation("wit:null => halt")] + public static IWorkItem NewWorkItem([NotNull] this IWorkItemType wit, [CanBeNull] IEnumerable> values) { + Contract.Requires(wit != null); + if (wit == null) throw new ArgumentNullException(nameof(wit)); var wi = wit.NewWorkItem(); @@ -20,10 +27,18 @@ public static IWorkItem NewWorkItem(this IWorkItemType wit, IEnumerable halt")] public static IEnumerable NewWorkItems( - this IWorkItemType wit, - IEnumerable>> values) + [NotNull] this IWorkItemType wit, + [InstantHandle] [NotNull] IEnumerable>> values) { + Contract.Requires(values != null); + Contract.Requires(wit != null); + + if (wit == null) throw new ArgumentNullException(nameof(wit)); + if (values == null) throw new ArgumentNullException(nameof(values)); + + return values.Select(wit.NewWorkItem); } } diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index 12fded67..1653edf5 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -1,8 +1,11 @@ using Microsoft.VisualStudio.Services.Common; using System; +using System.Diagnostics.Contracts; using System.Globalization; using System.Text.RegularExpressions; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { /// @@ -33,9 +36,11 @@ public class IdentityFieldValue /// /// The identity. /// identity - public IdentityFieldValue(ITeamFoundationIdentity identity) - : this(identity?.DisplayName, identity?.Descriptor?.Identifier, identity?.TeamFoundationId.ToString()) + public IdentityFieldValue([NotNull] ITeamFoundationIdentity identity) + : this(identity.DisplayName, identity.Descriptor?.Identifier, identity.TeamFoundationId.ToString()) { + Contract.Requires(identity != null); + if (identity == null) throw new ArgumentNullException(nameof(identity)); } @@ -84,38 +89,38 @@ public IdentityFieldValue(string displayName) { DisplayPart = displayName; - if (!string.IsNullOrEmpty(displayName)) + if (string.IsNullOrEmpty(displayName)) return; + + + if (TryGetVsid(displayName, out Guid guid2, out string str)) { - if (TryGetVsid(displayName, out Guid guid2, out string str)) - { - DisplayPart = str; - return; - } - if (TryGetDomainAndAccountName(displayName, out string str2)) - { - AccountName = str2; + DisplayPart = str; + return; + } + if (TryGetDomainAndAccountName(displayName, out string str2)) + { + AccountName = str2; - var strArray = str2.Split(IdentityConstants.DomainAccountNameSeparator); - if (strArray.Length != 2) return; + var strArray = str2.Split(IdentityConstants.DomainAccountNameSeparator); + if (strArray.Length != 2) return; - Domain = strArray[0]; - LogonName = strArray[1]; + Domain = strArray[0]; + LogonName = strArray[1]; - return; - } - if (TryGetAccountName(displayName, out str2)) + return; + } + if (TryGetAccountName(displayName, out str2)) + { + AccountName = str2; + if (str2.Contains("@")) { - AccountName = str2; - if (str2.Contains("@")) - { - Email = str2; - LogonName = str2.Split('@')[0]; - } - DisplayPart = displayName; - return; + Email = str2; + LogonName = str2.Split('@')[0]; } - if (TryGetDisplayName(displayName, out str2)) DisplayPart = str2; + DisplayPart = displayName; + return; } + if (TryGetDisplayName(displayName, out str2)) DisplayPart = str2; } /// diff --git a/src/Qwiq.Core/Link.cs b/src/Qwiq.Core/Link.cs index ab6b9525..9a8afe82 100644 --- a/src/Qwiq.Core/Link.cs +++ b/src/Qwiq.Core/Link.cs @@ -1,12 +1,17 @@ +using System; using System.Diagnostics; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public abstract class Link : ILink { [DebuggerStepThrough] - protected internal Link(string comment, BaseLinkType baseType) + protected internal Link([CanBeNull] string comment, BaseLinkType baseType) { + if (baseType == BaseLinkType.None) throw new ArgumentOutOfRangeException(nameof(baseType)); + Comment = comment; BaseType = baseType; } diff --git a/src/Qwiq.Core/NodeComparer.cs b/src/Qwiq.Core/NodeComparer.cs index 53efd0f1..96a55e7c 100644 --- a/src/Qwiq.Core/NodeComparer.cs +++ b/src/Qwiq.Core/NodeComparer.cs @@ -1,5 +1,7 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class NodeComparer : GenericComparer @@ -23,7 +25,7 @@ public override bool Equals(INode x, INode y) && string.Equals(x.Path, y.Path, StringComparison.OrdinalIgnoreCase); } - public override int GetHashCode(INode obj) + public override int GetHashCode([CanBeNull] INode obj) { if (ReferenceEquals(obj, null)) return 0; diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 5f3185f5..9a407aab 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -64,6 +64,9 @@ Properties\AssemblyInfo.Common.cs + + Properties\ReSharper.Annotations.cs + @@ -179,6 +182,7 @@ + diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs index 6f9afef5..3377b27e 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs @@ -1,28 +1,37 @@ +using JetBrains.Annotations; using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; namespace Microsoft.Qwiq { - public abstract class ReadOnlyObjectWithIdCollection : ReadOnlyObjectWithNameCollection, IReadOnlyObjectWithIdCollection + public abstract class ReadOnlyObjectWithIdCollection : ReadOnlyObjectWithNameCollection, + IReadOnlyObjectWithIdCollection where T : IIdentifiable { private readonly Func _idFunc; + private readonly IDictionary _mapById; - protected ReadOnlyObjectWithIdCollection(IEnumerable items, Func nameFunc) - :this(items, nameFunc, arg => arg.Id) + protected ReadOnlyObjectWithIdCollection([CanBeNull] IEnumerable items, [CanBeNull] Func nameFunc) + : this(items, nameFunc, arg => arg.Id) { } - protected ReadOnlyObjectWithIdCollection(IEnumerable items, Func nameFunc, Func idFunc) - :base(items, nameFunc) + protected ReadOnlyObjectWithIdCollection( + [CanBeNull] IEnumerable items, + [CanBeNull] Func nameFunc, + [NotNull] Func idFunc) + : base(items, nameFunc) { + Contract.Requires(idFunc != null); + _idFunc = idFunc ?? throw new ArgumentNullException(nameof(idFunc)); _mapById = new Dictionary(); } - protected ReadOnlyObjectWithIdCollection(IEnumerable items) - :base(items) + protected ReadOnlyObjectWithIdCollection([CanBeNull] IEnumerable items) + : base(items) { _idFunc = a => a.Id; _mapById = new Dictionary(); @@ -30,10 +39,31 @@ protected ReadOnlyObjectWithIdCollection(IEnumerable items) public virtual bool Contains(TId id) { - base.Ensure(); + Ensure(); return _mapById.ContainsKey(id); } + public virtual bool Equals(IReadOnlyObjectWithIdCollection other) + { + return ReadOnlyCollectionWithIdComparer.Default.Equals(this, other); + } + + public override bool Equals(object obj) + { + return ReadOnlyCollectionWithIdComparer.Default.Equals(this, obj as IReadOnlyObjectWithIdCollection); + } + + public virtual T GetById(TId id) + { + if (!TryGetById(id, out T byId)) throw new DeniedOrNotExistException(); + return byId; + } + + public override int GetHashCode() + { + return ReadOnlyCollectionWithIdComparer.Default.GetHashCode(this); + } + public virtual bool TryGetById(TId id, out T value) { Ensure(); @@ -46,12 +76,6 @@ public virtual bool TryGetById(TId id, out T value) return false; } - public virtual T GetById(TId id) - { - if (!TryGetById(id, out T byId)) throw new DeniedOrNotExistException(); - return byId; - } - protected override void Add(T value, int index) { base.Add(value, index); @@ -71,20 +95,5 @@ protected void AddById(TId id, int index) throw new ArgumentException($"An item with the ID {id} already exists.", e); } } - - public virtual bool Equals(IReadOnlyObjectWithIdCollection other) - { - return ReadOnlyCollectionWithIdComparer.Default.Equals(this, other); - } - - public override bool Equals(object obj) - { - return ReadOnlyCollectionWithIdComparer.Default.Equals(this, obj as IReadOnlyObjectWithIdCollection); - } - - public override int GetHashCode() - { - return ReadOnlyCollectionWithIdComparer.Default.GetHashCode(this); - } } } \ No newline at end of file diff --git a/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs index 901b5f01..747d060c 100644 --- a/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs +++ b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs @@ -2,8 +2,11 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { /// @@ -14,6 +17,7 @@ public abstract class ReadOnlyObjectWithNameCollection : IReadOnlyObjectWithN { private readonly object _lockObj = new object(); + [CanBeNull] private readonly Func _nameFunc; private bool _alreadyInit; @@ -24,19 +28,22 @@ public abstract class ReadOnlyObjectWithNameCollection : IReadOnlyObjectWithN private IDictionary _mapByName; - protected ReadOnlyObjectWithNameCollection(Func> itemFactory, Func nameFunc) + protected ReadOnlyObjectWithNameCollection([NotNull] Func> itemFactory, [NotNull] Func nameFunc) { + Contract.Requires(itemFactory != null); + Contract.Requires(nameFunc != null); + ItemFactory = itemFactory ?? throw new ArgumentNullException(nameof(itemFactory)); _nameFunc = nameFunc ?? throw new ArgumentNullException(nameof(nameFunc)); } - protected ReadOnlyObjectWithNameCollection(IEnumerable items, Func nameFunc) + protected ReadOnlyObjectWithNameCollection([CanBeNull] IEnumerable items, [CanBeNull] Func nameFunc) { ItemFactory = () => items ?? Enumerable.Empty(); _nameFunc = nameFunc; } - protected ReadOnlyObjectWithNameCollection(IEnumerable items) + protected ReadOnlyObjectWithNameCollection([CanBeNull] IEnumerable items) : this(items, null) { } diff --git a/src/Qwiq.Core/RevisionInternalContract.cs b/src/Qwiq.Core/RevisionInternalContract.cs new file mode 100644 index 00000000..a3dc2b41 --- /dev/null +++ b/src/Qwiq.Core/RevisionInternalContract.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.Contracts; + +namespace Microsoft.Qwiq +{ + [ContractClassFor(typeof(IRevisionInternal))] + internal abstract class RevisionInternalContract : IRevisionInternal + { + public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) + { + Contract.Requires(fieldDefinition != null); + + return default(object); + } + + public void SetFieldValue(IFieldDefinition fieldDefinition, object value) + { + Contract.Requires(fieldDefinition != null); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index b2b4dbed..2fccec80 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { @@ -21,14 +24,18 @@ protected internal WorkItem(IDictionary fields) { } - protected internal WorkItem(IWorkItemType type) + protected internal WorkItem([NotNull] IWorkItemType type) { + Contract.Requires(type != null); + _type = type ?? throw new ArgumentNullException(nameof(type)); _fields = new Lazy(()=> new FieldCollection(this, Type.FieldDefinitions, (revision, definition) => new Field(revision, definition))); } - protected internal WorkItem(IWorkItemType type, Func fieldCollectionFactory) + protected internal WorkItem([NotNull] IWorkItemType type, Func fieldCollectionFactory) { + Contract.Requires(type != null); + _type = type ?? throw new ArgumentNullException(nameof(type)); _fields = new Lazy(fieldCollectionFactory); } diff --git a/src/Qwiq.Core/WorkItemCollection.cs b/src/Qwiq.Core/WorkItemCollection.cs index b54ee15a..d565504b 100644 --- a/src/Qwiq.Core/WorkItemCollection.cs +++ b/src/Qwiq.Core/WorkItemCollection.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class WorkItemCollection : ReadOnlyObjectWithIdCollection, IWorkItemCollection @@ -10,13 +12,13 @@ public WorkItemCollection(IEnumerable workItems) } /// - public bool Equals(IWorkItemCollection other) + public bool Equals([CanBeNull] IWorkItemCollection other) { return Comparer.WorkItemCollection.Equals(this, other); } /// - public override bool Equals(object obj) + public override bool Equals([CanBeNull] object obj) { return Comparer.WorkItemCollection.Equals(this, obj as IWorkItemCollection); } diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index e9f4e6a5..fd77bc29 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -6,7 +6,6 @@ namespace Microsoft.Qwiq public abstract class WorkItemCommon : WorkItemCore, IWorkItemCommon, IEquatable { protected internal WorkItemCommon() - :base() { } diff --git a/src/Qwiq.Core/WorkItemCopyFlags.cs b/src/Qwiq.Core/WorkItemCopyFlags.cs index a9f05c8d..a4d953a2 100644 --- a/src/Qwiq.Core/WorkItemCopyFlags.cs +++ b/src/Qwiq.Core/WorkItemCopyFlags.cs @@ -2,6 +2,10 @@ namespace Microsoft.Qwiq { + /// + /// Flags specifying optional work item data that should be copied. + /// + /// [Flags] public enum WorkItemCopyFlags { diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index 2bdf1094..b3cd664d 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public abstract class WorkItemCore : IWorkItemCore, IEquatable, IRevisionInternal @@ -12,7 +14,7 @@ protected internal WorkItemCore() { } - protected internal WorkItemCore(IDictionary fields) + protected internal WorkItemCore([CanBeNull] IDictionary fields) { _fields = fields ?? new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -79,6 +81,7 @@ public override int GetHashCode() return NullableIdentifiableComparer.Default.GetHashCode(this); } + [CanBeNull] public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) { if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); diff --git a/src/Qwiq.Core/WorkItemType.cs b/src/Qwiq.Core/WorkItemType.cs index 6235472f..763347ad 100644 --- a/src/Qwiq.Core/WorkItemType.cs +++ b/src/Qwiq.Core/WorkItemType.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { @@ -7,11 +10,15 @@ public class WorkItemType : IWorkItemType, IEquatable private IFieldDefinitionCollection _fdc; internal WorkItemType( - string name, - string description, - Lazy fieldDefinitions, + [NotNull] string name, + [CanBeNull] string description, + [NotNull] Lazy fieldDefinitions, Func workItemFactory = null) { + Contract.Requires(name != null); + Contract.Requires(!string.IsNullOrEmpty(name)); + Contract.Requires(fieldDefinitions != null); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); FieldDefinitionFactory = () => fieldDefinitions.Value; @@ -24,7 +31,7 @@ internal WorkItemType( protected internal Func WorkItemFactory { get; internal set; } - public bool Equals(IWorkItemType other) + public bool Equals([CanBeNull] IWorkItemType other) { return WorkItemTypeComparer.Default.Equals(this, other); } @@ -50,6 +57,7 @@ public override int GetHashCode() return WorkItemTypeComparer.Default.GetHashCode(this); } + [NotNull] public override string ToString() { return Name; diff --git a/src/Qwiq.Core/WorkItemTypeComparer.cs b/src/Qwiq.Core/WorkItemTypeComparer.cs index 89aca3a4..4e8c238e 100644 --- a/src/Qwiq.Core/WorkItemTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeComparer.cs @@ -1,5 +1,7 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class WorkItemTypeComparer : GenericComparer @@ -10,7 +12,7 @@ private WorkItemTypeComparer() { } - public override bool Equals(IWorkItemType x, IWorkItemType y) + public override bool Equals([CanBeNull] IWorkItemType x, [CanBeNull] IWorkItemType y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; diff --git a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs index c1a63a21..95c036cf 100644 --- a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs +++ b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Identity @@ -20,8 +23,10 @@ public class DisplayNameToAliasValueConverter : IIdentityValueConverter /// /// The identity management service. /// identityManagementService - public DisplayNameToAliasValueConverter(IIdentityManagementService identityManagementService) + public DisplayNameToAliasValueConverter([NotNull] IIdentityManagementService identityManagementService) { + Contract.Requires(identityManagementService != null); + _identityManagementService = identityManagementService ?? throw new ArgumentNullException(nameof(identityManagementService)); } @@ -30,7 +35,7 @@ public DisplayNameToAliasValueConverter(IIdentityManagementService identityManag /// /// The value to convert. /// A instance whose key is the and value is is equivalent to the value of . - public object Map(object value) + public object Map([CanBeNull] object value) { if (value is string stringValue) return GetIdentityNames(stringValue); diff --git a/src/Qwiq.Identity/IIdentityValueConverter.cs b/src/Qwiq.Identity/IIdentityValueConverter.cs index 0bd73643..acf45861 100644 --- a/src/Qwiq.Identity/IIdentityValueConverter.cs +++ b/src/Qwiq.Identity/IIdentityValueConverter.cs @@ -1,3 +1,5 @@ +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Identity { /// @@ -10,6 +12,7 @@ public interface IIdentityValueConverter /// /// The value to convert. /// An instance whose value is equivalent to the value of . - object Map(object value); + [ContractAnnotation("null => null; notnull => notnull")] + object Map([CanBeNull] object value); } } diff --git a/src/Qwiq.Identity/IdentityAliasValueConverter.cs b/src/Qwiq.Identity/IdentityAliasValueConverter.cs index 0370bfbe..6920427d 100644 --- a/src/Qwiq.Identity/IdentityAliasValueConverter.cs +++ b/src/Qwiq.Identity/IdentityAliasValueConverter.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; +using JetBrains.Annotations; + using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Identity @@ -32,10 +35,16 @@ public class IdentityAliasValueConverter : IIdentityValueConverter /// var mapper = new IdentityAliasMapper(ims, "CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C", "contoso.com"); /// public IdentityAliasValueConverter( - IIdentityManagementService identityManagementService, - string tenantId, - params string[] domains) + [NotNull] IIdentityManagementService identityManagementService, + [NotNull] string tenantId, + [NotNull] [ItemNotNull] params string[] domains) { + Contract.Requires(!string.IsNullOrEmpty(tenantId)); + Contract.Requires(identityManagementService != null); + Contract.Requires(domains != null); + Contract.Requires(domains.Length > 0); + Contract.Requires(domains.All(item => item != null)); + if (domains == null) throw new ArgumentNullException(nameof(domains)); if (string.IsNullOrEmpty(tenantId)) throw new ArgumentException("Value cannot be null or empty.", nameof(tenantId)); if (domains.Length == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(domains)); @@ -50,7 +59,8 @@ public IdentityAliasValueConverter( /// The value. /// An instance whose value is equivilent to the value of . /// "danj" becomes "danj@contoso.com" - public object Map(object value) + [ContractAnnotation("null => null; notnull => notnull")] + public object Map([CanBeNull] object value) { if (value is string stringValue) return GetIdentityNames(stringValue).Single(); @@ -100,7 +110,7 @@ private IDictionary> CreatePossibleIden var loggedInAccountString = $"{tenantId}\\{alias}@{domain}".ToString(CultureInfo.InvariantCulture); descriptorsForAlias.Add(_identityManagementService.CreateIdentityDescriptor(IdentityConstants.ClaimsType, loggedInAccountString)); - descriptorsForAlias.Add(_identityManagementService.CreateIdentityDescriptor(IdentityConstants.BindPendingIdentityType,IdentityConstants.BindPendingSidPrefix + loggedInAccountString)); + descriptorsForAlias.Add(_identityManagementService.CreateIdentityDescriptor(IdentityConstants.BindPendingIdentityType, IdentityConstants.BindPendingSidPrefix + loggedInAccountString)); } descriptors.Add(alias, descriptorsForAlias); diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 088474e0..3e6e5061 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -21,6 +21,9 @@ Properties\AssemblyInfo.Common.cs + + Properties\ReSharper.Annotations.cs + diff --git a/src/Qwiq.Linq/CachingFieldMapper.cs b/src/Qwiq.Linq/CachingFieldMapper.cs index 689a7278..8ef3e223 100644 --- a/src/Qwiq.Linq/CachingFieldMapper.cs +++ b/src/Qwiq.Linq/CachingFieldMapper.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq.Linq { @@ -9,9 +12,11 @@ public class CachingFieldMapper : IFieldMapper private readonly IFieldMapper _innerMapper; private readonly ConcurrentDictionary _cache; - public CachingFieldMapper(IFieldMapper innerMapper) + public CachingFieldMapper([NotNull] IFieldMapper innerMapper) { - _innerMapper = innerMapper; + Contract.Requires(innerMapper != null); + + _innerMapper = innerMapper ?? throw new ArgumentNullException(nameof(innerMapper)); _cache = new ConcurrentDictionary(); } @@ -36,8 +41,12 @@ private T GetOrAdd(string key, Func func) return (T)_cache.GetOrAdd(key, val => func()); } - private string GenerateCacheKey(Type type, string method, string propertyName = "") + private string GenerateCacheKey([NotNull] Type type, [NotNull] string method, [NotNull] string propertyName = "") { + Contract.Requires(type != null); + Contract.Requires(method != null); + Contract.Requires(propertyName != null); + return type.AssemblyQualifiedName + method + propertyName; } } diff --git a/src/Qwiq.Linq/FieldMapperContract.cs b/src/Qwiq.Linq/FieldMapperContract.cs new file mode 100644 index 00000000..0b8816ae --- /dev/null +++ b/src/Qwiq.Linq/FieldMapperContract.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Microsoft.Qwiq.Linq +{ + [ContractClassFor(typeof(IFieldMapper))] + internal abstract class FieldMapperContract : IFieldMapper + { + public string GetFieldName(Type type, string propertyName) + { + Contract.Requires(type != null); + Contract.Requires(!string.IsNullOrEmpty(propertyName)); + + return default(string); + } + + public IEnumerable GetFieldNames(Type type) + { + Contract.Requires(type != null); + + return default(IEnumerable); + } + + public IEnumerable GetWorkItemType(Type type) + { + Contract.Requires(type != null); + + return default(IEnumerable); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Linq/IFieldMapper.cs b/src/Qwiq.Linq/IFieldMapper.cs index 179734bd..fc1e4ef0 100644 --- a/src/Qwiq.Linq/IFieldMapper.cs +++ b/src/Qwiq.Linq/IFieldMapper.cs @@ -1,11 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq.Linq { /// /// Maps friendly names to and from reference names /// + [ContractClass(typeof(FieldMapperContract))] public interface IFieldMapper { /// @@ -13,14 +17,14 @@ public interface IFieldMapper /// /// The type of to get the name of. /// The [Work Item Type] name for the sub-type. - IEnumerable GetWorkItemType(Type type); + IEnumerable GetWorkItemType([NotNull] Type type); /// /// Given a specific work item sub-type, get the TFS field names needed to populate the type. /// /// The type of to get the fields for. /// The list of strings of field names for the sub-type. - IEnumerable GetFieldNames(Type type); + IEnumerable GetFieldNames([NotNull] Type type); /// /// Given a specific work item sub-type and property name, get the associated TFS field name. @@ -31,6 +35,6 @@ public interface IFieldMapper /// For example, calling GetFieldName(typeof(Bug), "OpenedBy") returns "[Created By]" /// /// The TFS field name that matches the property name. - string GetFieldName(Type type, string propertyName); + string GetFieldName([NotNull] Type type, [NotNull] string propertyName); } } diff --git a/src/Qwiq.Linq/Projector.cs b/src/Qwiq.Linq/Projector.cs index b384f0e6..ab5395c7 100644 --- a/src/Qwiq.Linq/Projector.cs +++ b/src/Qwiq.Linq/Projector.cs @@ -1,16 +1,27 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Linq { public static class Projector { - public static object Project(IEnumerable projections, IEnumerable data) + public static object Project([NotNull] IEnumerable projections, [NotNull] IEnumerable data) { + if (projections == null) throw new ArgumentNullException(nameof(projections)); + if (data == null) throw new ArgumentNullException(nameof(data)); + Contract.Requires(projections != null); + Contract.Requires(data != null); + var projectedData = data; foreach (var projection in projections) { + Debug.Assert(projection != null, "projection != null"); var compiledProjection = projection.Compile(); projectedData = projectedData.Select(r => compiledProjection.DynamicInvoke(r)); } diff --git a/src/Qwiq.Linq/Query.cs b/src/Qwiq.Linq/Query.cs index 7d2131bc..5193ffe0 100644 --- a/src/Qwiq.Linq/Query.cs +++ b/src/Qwiq.Linq/Query.cs @@ -2,9 +2,12 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Linq { // Boiler plate @@ -18,21 +21,29 @@ public sealed class Query : IOrderedQueryable private readonly IQueryProvider _provider; - public Query(IQueryProvider provider, IWiqlQueryBuilder builder) + public Query([NotNull] IQueryProvider provider, [NotNull] IWiqlQueryBuilder builder) { + Contract.Requires(provider != null); + Contract.Requires(builder != null); + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _builder = builder; + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); _expression = Expression.Constant(this); } - public Query(IQueryProvider provider, IWiqlQueryBuilder builder, Expression expression) + public Query([NotNull] IQueryProvider provider, [NotNull] IWiqlQueryBuilder builder, [NotNull] Expression expression) { + Contract.Requires(provider != null); + Contract.Requires(builder != null); + Contract.Requires(expression != null); + if (expression == null) throw new ArgumentNullException(nameof(expression)); if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) throw new ArgumentOutOfRangeException(nameof(expression)); _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _builder = builder; - _expression = expression; + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); + _expression = expression ?? throw new ArgumentNullException(nameof(expression)); + ; } Type IQueryable.ElementType => typeof(T); diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index e4b3c152..ebc14b48 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -23,7 +23,11 @@ Properties\AssemblyInfo.Common.cs + + Properties\ReSharper.Annotations.cs + + diff --git a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs index 72fa79ef..38fb8e8d 100644 --- a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; @@ -46,6 +47,9 @@ public BulkIdentityAwareAttributeMapperStrategy( IIdentityValueConverter identityValueConverter ) { + Contract.Requires(inspector != null); + Contract.Requires(identityValueConverter != null); + _inspector = inspector ?? throw new ArgumentNullException(nameof(inspector)); _displayNameToAliasValueConverter = identityValueConverter ?? throw new ArgumentNullException(nameof(identityValueConverter)); } diff --git a/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs b/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs index 426e1a32..d70f4292 100644 --- a/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs +++ b/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Linq.Expressions; @@ -22,6 +23,8 @@ public class IdentityFieldAttributeVisitor : ExpressionVisitor /// An instance of used to convert identity values. public IdentityFieldAttributeVisitor(IIdentityValueConverter valueConverter) { + Contract.Requires(valueConverter != null); + _valueConverter = valueConverter ?? throw new ArgumentNullException(nameof(valueConverter)); } diff --git a/src/Qwiq.Mapper/FieldMapper.cs b/src/Qwiq.Mapper/FieldMapper.cs index aa90e73c..287191d3 100644 --- a/src/Qwiq.Mapper/FieldMapper.cs +++ b/src/Qwiq.Mapper/FieldMapper.cs @@ -1,6 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; + +using JetBrains.Annotations; + using Microsoft.Qwiq.Linq; using Microsoft.Qwiq.Mapper.Attributes; @@ -10,6 +14,7 @@ public class FieldMapper : IFieldMapper { public IEnumerable GetWorkItemType(Type type) { + if (type == null) throw new ArgumentNullException(nameof(type)); var customAttributes = type.GetCustomAttributes(typeof(WorkItemTypeAttribute), true).Cast().ToList(); return customAttributes.Select(ca => ca.GetTypeName()).OrderBy(name => name); // Order alphabetically so string comparisons work and we don't needlessly permute our queries @@ -41,8 +46,12 @@ public string GetFieldName(Type type, string propertyName) return fieldName; } - private static T GetFieldAttribute(Type type, string propertyName) + [CanBeNull] + private static T GetFieldAttribute([NotNull] Type type, [NotNull] string propertyName) { + Contract.Requires(type != null); + Contract.Requires(!string.IsNullOrEmpty(propertyName)); + var property = type.GetProperty(propertyName); var customAttributes = Enumerable.Empty(); diff --git a/src/Qwiq.Mapper/IWorkItemMapper.cs b/src/Qwiq.Mapper/IWorkItemMapper.cs index 65a59448..b843ba26 100644 --- a/src/Qwiq.Mapper/IWorkItemMapper.cs +++ b/src/Qwiq.Mapper/IWorkItemMapper.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq.Mapper { + [ContractClass(typeof(WorkItemMapperContract))] public interface IWorkItemMapper { /// @@ -15,17 +19,19 @@ public interface IWorkItemMapper /// The subclass of Issue that the work items should become /// The set of TFS WorkItems to convert /// A cloned set of -subclassed items. - IEnumerable Create(IEnumerable collection) where T : IIdentifiable, new(); + IEnumerable Create([NotNull] IEnumerable collection) where T : IIdentifiable, new(); - IEnumerable> Create(Type type, IEnumerable collection); + IEnumerable> Create([NotNull] Type type, [NotNull] IEnumerable collection); /// /// Create a new, empty work item sub-class. /// /// The type of sub-class to create /// A new work item of type T with the default value for all fields. + [NotNull] T Default() where T : new(); + [ItemNotNull] IEnumerable MapperStrategies { get; } } } diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index acfce312..766a13f8 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -27,6 +27,9 @@ Properties\AssemblyInfo.Common.cs + + Properties\ReSharper.Annotations.cs + @@ -44,6 +47,7 @@ + diff --git a/src/Qwiq.Mapper/WorkItemMapper.cs b/src/Qwiq.Mapper/WorkItemMapper.cs index 57c92449..2284f239 100644 --- a/src/Qwiq.Mapper/WorkItemMapper.cs +++ b/src/Qwiq.Mapper/WorkItemMapper.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Mapper { public class WorkItemMapper : IWorkItemMapper @@ -13,9 +16,11 @@ public class WorkItemMapper : IWorkItemMapper private delegate IIdentifiable ObjectActivator(); private static readonly ConcurrentDictionary OptimizedCtorExpression = new ConcurrentDictionary(); - public WorkItemMapper(IEnumerable mapperStrategies) + public WorkItemMapper([NotNull] IEnumerable mapperStrategies) { - MapperStrategies = mapperStrategies.ToList(); + Contract.Requires(mapperStrategies != null); + + MapperStrategies = mapperStrategies?.ToList() ?? throw new ArgumentNullException(nameof(mapperStrategies)); } public T Default() where T : new() @@ -59,8 +64,12 @@ public WorkItemMapper(IEnumerable mapperStrategies) return workItemsToMap.Select(wi => wi.Value); } - private static ObjectActivator OptimizedCtorExpressionCache(Type type) + [NotNull] + private static ObjectActivator OptimizedCtorExpressionCache([NotNull] Type type) { + Contract.Requires(type != null); + Contract.Ensures(Contract.Result() != null); + return OptimizedCtorExpression.GetOrAdd( type.TypeHandle, handle => diff --git a/src/Qwiq.Mapper/WorkItemMapperContract.cs b/src/Qwiq.Mapper/WorkItemMapperContract.cs new file mode 100644 index 00000000..48e45b74 --- /dev/null +++ b/src/Qwiq.Mapper/WorkItemMapperContract.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Microsoft.Qwiq.Mapper +{ + [ContractClassFor(typeof(IWorkItemMapper))] + internal abstract class WorkItemMapperContract : IWorkItemMapper + { + public IEnumerable Create(IEnumerable collection) + where T : IIdentifiable, new() + { + Contract.Requires(collection != null); + + return default(IEnumerable); + } + + /// + IEnumerable IWorkItemMapper.Create(IEnumerable collection) + { + throw new NotImplementedException(); + } + + public IEnumerable> Create(Type type, IEnumerable collection) + { + Contract.Requires(type != null); + Contract.Requires(collection != null); + + return default(IEnumerable>); + } + + /// + public abstract T Default() + where T : new(); + + /// + public abstract IEnumerable MapperStrategies { get; } + } +} \ No newline at end of file diff --git a/src/ReSharper.Annotations.cs b/src/ReSharper.Annotations.cs new file mode 100644 index 00000000..ed33ff3b --- /dev/null +++ b/src/ReSharper.Annotations.cs @@ -0,0 +1,1272 @@ +/* MIT License + +Copyright (c) 2016 JetBrains http://www.jetbrains.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace JetBrains.Annotations +{ + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + internal enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3 + } + + [Flags] + internal enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + + /// Method only reads content of the collection but does not modify it. + Read = 1, + + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + [Flags] + internal enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + + /// Only entity marked with attribute considered used. + Access = 1, + + /// Indicates implicit assignment to a member. + Assign = 2, + + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8 + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + internal enum ImplicitUseTargetFlags + { + Default = Itself, + + Itself = 1, + + /// Members of entity marked with attribute are considered used. + Members = 2, + + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] + public Type ControlType { get; } + + [NotNull] + public string TagName { get; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + internal sealed class AspDataFieldAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + internal sealed class AspDataFieldsAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class AspMethodPropertyAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() + { + } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + internal sealed class AspMvcActionSelectorAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() + { + } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() + { + } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcDisplayTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcEditorTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcMasterAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcModelTypeAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcPartialViewAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + internal sealed class AspMvcSuppressViewErrorAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcViewComponentAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcViewComponentViewAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] + public string Attribute { get; } + } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class AspTypePropertyAttribute : Attribute + { + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + + public bool CreateConstructorReferences { get; } + } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; } + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class AssertionMethodAttribute : Attribute + { + } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + internal sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; } + } + + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + /// + [AttributeUsage( + AttributeTargets.Method + | AttributeTargets.Parameter + | AttributeTargets.Property + | AttributeTargets.Delegate + | AttributeTargets.Field + | AttributeTargets.Event + | AttributeTargets.Class + | AttributeTargets.Interface + | AttributeTargets.GenericParameter)] + internal sealed class CanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + internal sealed class CannotApplyEqualityOperatorAttribute : Attribute + { + } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + internal sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for method output + /// means that the methos doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by R# analysis.
+ ///
+ /// + /// + /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + internal sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) + { + } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] + public string Contract { get; } + + public bool ForceFullStates { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + internal sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + internal sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() + { + } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] + public string Name { get; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class InstantHandleAttribute : Attribute + { + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class InvokerParameterNameAttribute : Attribute + { + } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method + | AttributeTargets.Parameter + | AttributeTargets.Property + | AttributeTargets.Delegate + | AttributeTargets.Field)] + internal sealed class ItemCanBeNullAttribute : Attribute + { + } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method + | AttributeTargets.Parameter + | AttributeTargets.Property + | AttributeTargets.Delegate + | AttributeTargets.Field)] + internal sealed class ItemNotNullAttribute : Attribute + { + } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class LinqTunnelAttribute : Attribute + { + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() + : this(true) + { + } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; } + } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + internal sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// + /// > + public int Editable { get; set; } + + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] + public string Expression { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] + public string Target { get; set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + internal sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() + { + } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] + public string Justification { get; } + } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class NoEnumerationAttribute : Attribute + { + } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + internal sealed class NoReorderAttribute : Attribute + { + } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// + /// NotifyChanged(string) + /// + /// + /// NotifyChanged(params string[]) + /// + /// + /// NotifyChanged{T}(Expression{Func{T}}) + /// + /// + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// + /// + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// + /// NotifyChanged("Property") + /// + /// + /// NotifyChanged(() => Property) + /// + /// + /// NotifyChanged((VM x) => x.Property) + /// + /// + /// SetProperty(ref myField, value, "Property") + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() + { + } + + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] + public string ParameterName { get; } + } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + /// + [AttributeUsage( + AttributeTargets.Method + | AttributeTargets.Parameter + | AttributeTargets.Property + | AttributeTargets.Delegate + | AttributeTargets.Field + | AttributeTargets.Event + | AttributeTargets.Class + | AttributeTargets.Interface + | AttributeTargets.GenericParameter)] + internal sealed class NotNullAttribute : Attribute + { + } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() + { + } + + public PathReferenceAttribute([NotNull] [PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] + public string BasePath { get; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + /// + [AttributeUsage( + AttributeTargets.Field + | AttributeTargets.Property + | AttributeTargets.Parameter + | AttributeTargets.Method + | AttributeTargets.Class + | AttributeTargets.Interface + | AttributeTargets.Struct + | AttributeTargets.GenericParameter)] + internal sealed class ProvidesContextAttribute : Attribute + { + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + internal sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() + { + } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] + public string Comment { get; } + } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class PureAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] + public string Directive { get; } + } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorHelperCommonAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] + public string FieldName { get; } + + [NotNull] + public string Type { get; } + } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class RazorLayoutAttribute : Attribute + { + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class RazorSectionAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorWriteLiteralMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorWriteMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RazorWriteMethodParameterAttribute : Attribute + { + } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RegexPatternAttribute : Attribute + { + } + + /// + /// An extension method marked with this attribute is processed by ReSharper code completion + /// as a 'Source Template'. When extension method is completed over some expression, it's source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class SourceTemplateAttribute : Attribute + { + } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + /// + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate)] + internal sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] + public string FormatParameterName { get; } + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + internal sealed class TerminatesProgramAttribute : Attribute + { + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseTargetFlags TargetFlags { get; } + + public ImplicitUseKindFlags UseKindFlags { get; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] + internal sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + internal sealed class XamlItemBindingOfItemsControlAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + internal sealed class XamlItemsControlAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/Identity/IdentityMapperTests.cs b/test/Qwiq.Integration.Tests/Identity/IdentityMapperTests.cs index 7564e71e..5c639b08 100644 --- a/test/Qwiq.Integration.Tests/Identity/IdentityMapperTests.cs +++ b/test/Qwiq.Integration.Tests/Identity/IdentityMapperTests.cs @@ -37,7 +37,7 @@ public override void Given() var soapIms = ((IInternalTeamProjectCollection)Soap.TeamProjectCollection).GetService().AsProxy(); var translator = new WiqlTranslator(); - var idMapper = new IdentityAliasValueConverter(soapIms, "72F988BF-86F1-41AF-91AB-2D7CD011DB47", "microsoft.com"); + var idMapper = new IdentityAliasValueConverter(soapIms, IntegrationSettings.TenantId, IntegrationSettings.Domains); var visitors = new ExpressionVisitor[] { new PartialEvaluator(), new IdentityMappingVisitor(idMapper), new QueryRewriter() }; var soapBuilder = new WiqlQueryBuilder(translator, visitors); diff --git a/test/Qwiq.Mocks/CoreFieldDefinitions.cs b/test/Qwiq.Mocks/CoreFieldDefinitions.cs index abbf0a38..879e8ad7 100644 --- a/test/Qwiq.Mocks/CoreFieldDefinitions.cs +++ b/test/Qwiq.Mocks/CoreFieldDefinitions.cs @@ -1,4 +1,5 @@ -using System; +using JetBrains.Annotations; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -8,16 +9,29 @@ namespace Microsoft.Qwiq.Mocks [StructLayout(LayoutKind.Sequential, Size = 1)] public struct CoreFieldDefinitions { - public static IFieldDefinition Id { get; } = new MockFieldDefinition((int)CoreField.Id, CoreFieldRefNames.NameLookup[CoreFieldRefNames.Id], CoreFieldRefNames.Id); - public static IFieldDefinition WorkItemType { get; } = new MockFieldDefinition((int)CoreField.WorkItemType, CoreFieldRefNames.NameLookup[CoreFieldRefNames.WorkItemType], CoreFieldRefNames.WorkItemType); + [NotNull] + public static IFieldDefinition Id { get; } = + new MockFieldDefinition((int)CoreField.Id, CoreFieldRefNames.NameLookup[CoreFieldRefNames.Id], CoreFieldRefNames.Id); - public static IEnumerable All { get; } = CoreFieldRefNames - .ReferenceNameLookup - .Select(s => new MockFieldDefinition(CoreFieldRefNames.CoreFieldIdLookup[s.Value], s.Key, s.Value)); + [NotNull] + public static IFieldDefinition WorkItemType { get; } = new MockFieldDefinition( + (int)CoreField.WorkItemType, + CoreFieldRefNames.NameLookup[CoreFieldRefNames + .WorkItemType], + CoreFieldRefNames.WorkItemType); - public static IDictionary NameLookup { get; } = All.ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); - public static IDictionary ReferenceNameLookup { get; } = All.ToDictionary(k => k.ReferenceName, e => e, StringComparer.OrdinalIgnoreCase); + [ItemNotNull] + [NotNull] + public static IEnumerable All { get; } = CoreFieldRefNames + .ReferenceNameLookup.Select(s => new MockFieldDefinition(CoreFieldRefNames.CoreFieldIdLookup[s.Value], s.Key, s.Value)) + .ToList(); + [NotNull] + public static IDictionary NameLookup { get; } = + All.ToDictionary(k => k.Name, e => e, StringComparer.OrdinalIgnoreCase); + [NotNull] + public static IDictionary ReferenceNameLookup { get; } = + All.ToDictionary(k => k.ReferenceName, e => e, StringComparer.OrdinalIgnoreCase); } -} +} \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index 8c5da6a7..f40315d9 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -32,6 +32,9 @@ GenericComparer.cs + + Properties\ReSharper.Annotations.cs + From 943ffaa91b56ae9ff7bb3498f2a60686daff9899 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 28 Apr 2017 16:03:40 -0700 Subject: [PATCH 179/251] Split up code paths for when default value is known If the default value for a type is passed in, use the value rather than creating a new Func to use as a factory to a lazy. --- src/Qwiq.Core/TypeParser.cs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Qwiq.Core/TypeParser.cs b/src/Qwiq.Core/TypeParser.cs index 557588d9..7c7e0267 100644 --- a/src/Qwiq.Core/TypeParser.cs +++ b/src/Qwiq.Core/TypeParser.cs @@ -17,12 +17,12 @@ private TypeParser() public object Parse(Type destinationType, object value, object defaultValue) { - return ParseImpl(destinationType, value, new Lazy(() => defaultValue ?? GetDefaultValueOfType(destinationType))); + return ParseImpl(destinationType, value, defaultValue); } public object Parse(Type destinationType, object input) { - return ParseImpl(destinationType, input, new Lazy(() => GetDefaultValueOfType(destinationType))); + return ParseImpl(destinationType, input); } public T Parse(object value) @@ -45,8 +45,10 @@ private static bool IsGenericNullable(Type type) return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } - private static object ParseImpl(Type destinationType, object value, Lazy defaultValueFactory) + private static object ParseImpl(Type destinationType, object value) { + var defaultValueFactory = new Lazy(() => GetDefaultValueOfType(destinationType)); + // If the incoming value is null, return the default value if (ValueRepresentsNull(value)) return defaultValueFactory.Value; @@ -60,6 +62,21 @@ private static object ParseImpl(Type destinationType, object value, Lazy return null; } + private static object ParseImpl(Type destinationType, object value, object defaultValue) + { + // If the incoming value is null, return the default value + if (ValueRepresentsNull(value)) return defaultValue; + + // Quit if no type conversion is actually required + if (value.GetType() == destinationType) return value; + if (destinationType.IsInstanceOfType(value)) return value; + if (TryConvert(destinationType, value, out object result)) return result; + if (IsGenericNullable(destinationType) && defaultValue == null) return null; + if (TryConvert(destinationType, defaultValue, out result)) return result; + + return null; + } + private static bool TryConvert(Type destinationType, object value, out object result) { if (IsGenericNullable(destinationType)) From 9645a1cad84f23075a3a2b13a92b9c046cf543bf Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 28 Apr 2017 16:07:16 -0700 Subject: [PATCH 180/251] Remove boxing --- src/Qwiq.Core.Rest/WorkItem.cs | 2 +- src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs | 2 +- src/Qwiq.Core/Project.cs | 2 +- src/Qwiq.Core/TeamFoundationIdentity.cs | 10 +++++++--- src/Qwiq.Core/WorkItem.cs | 2 +- src/Qwiq.Core/WorkItemCommon.cs | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index db33a718..7c4a0027 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -18,7 +18,7 @@ internal WorkItem( TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, Lazy wit, Func linkFunc) - : base(item.Fields) + : base((Dictionary)item.Fields) { _item = item; _wit = wit; diff --git a/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs b/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs index 239bc251..904ee393 100644 --- a/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs +++ b/src/Qwiq.Core/Exceptions/DeniedOrNotExistException.cs @@ -37,7 +37,7 @@ public DeniedOrNotExistException(string projectName) /// The project unique identifier. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")] public DeniedOrNotExistException(Guid projectGuid) - : base(string.Format(CultureInfo.InvariantCulture, ErrorFormat, projectGuid)) + : base(string.Format(CultureInfo.InvariantCulture, ErrorFormat, projectGuid.ToString())) { } diff --git a/src/Qwiq.Core/Project.cs b/src/Qwiq.Core/Project.cs index 7fce91ba..bf524fde 100644 --- a/src/Qwiq.Core/Project.cs +++ b/src/Qwiq.Core/Project.cs @@ -60,7 +60,7 @@ public override int GetHashCode() public override string ToString() { - FormattableString s = $"{Guid} ({Name})"; + FormattableString s = $"{Guid.ToString()} ({Name})"; return s.ToString(CultureInfo.InvariantCulture); } diff --git a/src/Qwiq.Core/TeamFoundationIdentity.cs b/src/Qwiq.Core/TeamFoundationIdentity.cs index c489568b..24b60708 100644 --- a/src/Qwiq.Core/TeamFoundationIdentity.cs +++ b/src/Qwiq.Core/TeamFoundationIdentity.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.Services.Common; using System; using System.Collections.Generic; +using System.Globalization; namespace Microsoft.Qwiq { @@ -82,8 +83,8 @@ public virtual string UniqueName else { _uniqueName = string.IsNullOrEmpty(domain) - ? $"{account}:{UniqueUserId}" - : $"{string.Format(IdentityConstants.DomainQualifiedAccountNameFormat, domain, account)}:{ UniqueUserId}"; + ? $"{account}:{UniqueUserId.ToString(CultureInfo.InvariantCulture)}" + : $"{string.Format(IdentityConstants.DomainQualifiedAccountNameFormat, domain, account)}:{UniqueUserId.ToString(CultureInfo.InvariantCulture)}"; } return _uniqueName; @@ -110,7 +111,10 @@ public override int GetHashCode() public override string ToString() { - return $"Identity {TeamFoundationId} (IdentityType: {(Descriptor == null ? string.Empty : Descriptor.IdentityType)}; Identifier: {(Descriptor == null ? string.Empty : Descriptor.Identifier)}; DisplayName: {DisplayName})"; + // Call of .ToString to avoid boxing Guid to Object + // ReSharper disable RedundantToStringCallForValueType + return $"Identity {TeamFoundationId.ToString()} (IdentityType: {(Descriptor == null ? string.Empty : Descriptor.IdentityType)}; Identifier: {(Descriptor == null ? string.Empty : Descriptor.Identifier)}; DisplayName: {DisplayName})"; + // ReSharper restore RedundantToStringCallForValueType } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index 2fccec80..9a6701bd 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -19,7 +19,7 @@ public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable _fields; - protected internal WorkItem(IDictionary fields) + protected internal WorkItem(Dictionary fields) : base(fields) { } diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index fd77bc29..05109c16 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -9,7 +9,7 @@ protected internal WorkItemCommon() { } - protected internal WorkItemCommon(IDictionary fields) + protected internal WorkItemCommon(Dictionary fields) :base(fields) { } From 3061834fd33cbceeca4fd367a1238a8ef41250f7 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 28 Apr 2017 16:08:34 -0700 Subject: [PATCH 181/251] Update Query.cs - Add code contract and JetBrains annotations for parameter expectations. - Force materialization of RunLinkQuery --- src/Qwiq.Core.Soap/Query.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index dc857532..582ac73f 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.Common; @@ -13,8 +16,12 @@ internal class Query : IQuery private readonly TeamFoundation.WorkItemTracking.Client.Query _query; - internal Query(TeamFoundation.WorkItemTracking.Client.Query query, int pageSize = PageSizeLimits.DefaultPageSize) + internal Query([NotNull] TeamFoundation.WorkItemTracking.Client.Query query, int pageSize = PageSizeLimits.DefaultPageSize) { + Contract.Requires(query != null); + Contract.Requires(pageSize < PageSizeLimits.MaxPageSize); + Contract.Requires(pageSize > PageSizeLimits.DefaultPageSize); + _pageSize = pageSize; _query = query ?? throw new ArgumentNullException(nameof(query)); @@ -32,6 +39,7 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() public IEnumerable RunLinkQuery() { + // REVIEW: Create an IWorkItemLinkInfo like IWorkItemLinkTypeEndCollection and IWorkItemCollection var ends = new Lazy(() => new WorkItemLinkTypeEndCollection(GetLinkTypes())); return _query.RunLinkQuery() @@ -41,7 +49,9 @@ public IEnumerable RunLinkQuery() IWorkItemLinkTypeEnd LinkTypeEndFactory() => ends.Value.TryGetById(item.LinkTypeId, out IWorkItemLinkTypeEnd end) ? end : null; return new WorkItemLinkInfo(item.SourceId, item.TargetId, new Lazy(LinkTypeEndFactory)); - }); + }) + .ToList() + .AsReadOnly(); } public IWorkItemCollection RunQuery() From b2d4e5046c9bf4ed629e0b842febe7f0f5f5789c Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 28 Apr 2017 16:09:43 -0700 Subject: [PATCH 182/251] Update WorkItemLinkInfo.cs - Add JetBrains annotations and code contracts - Update LinkType property to avoid a NullReferenceException in certain scenarios --- src/Qwiq.Core/WorkItemLinkInfo.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Qwiq.Core/WorkItemLinkInfo.cs b/src/Qwiq.Core/WorkItemLinkInfo.cs index 4108133a..f766b841 100644 --- a/src/Qwiq.Core/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core/WorkItemLinkInfo.cs @@ -1,29 +1,36 @@ using System; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Globalization; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class WorkItemLinkInfo : IWorkItemLinkInfo { + [CanBeNull] private readonly IWorkItemLinkTypeEnd _id; + [CanBeNull] private readonly Lazy _lazyId; - internal WorkItemLinkInfo(int sourceId, int targetId, IWorkItemLinkTypeEnd id) + internal WorkItemLinkInfo(int sourceId, int targetId, [NotNull] IWorkItemLinkTypeEnd id) : this(sourceId, targetId, (Lazy) null) { + Contract.Requires(id != null); + _id = id ?? throw new ArgumentNullException(nameof(id)); } - internal WorkItemLinkInfo(int sourceId, int targetId, Lazy lazyId) + internal WorkItemLinkInfo(int sourceId, int targetId, [CanBeNull] Lazy lazyId) { SourceId = sourceId; TargetId = targetId; _lazyId = lazyId; } - public IWorkItemLinkTypeEnd LinkType => _id ?? _lazyId.Value; + public IWorkItemLinkTypeEnd LinkType => _id ?? _lazyId?.Value ?? throw new InvalidOperationException(); public int SourceId { get; } From c6ea30e40cddfaaebef2ceafa496a4578f28bad4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 28 Apr 2017 16:10:50 -0700 Subject: [PATCH 183/251] Update WorkItemCore.cs When retrieving a value using `GetValue` generic, perform some optimization for string types. --- src/Qwiq.Core/WorkItemCore.cs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index b3cd664d..639bba2c 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using JetBrains.Annotations; @@ -7,14 +8,14 @@ namespace Microsoft.Qwiq { public abstract class WorkItemCore : IWorkItemCore, IEquatable, IRevisionInternal { - private readonly IDictionary _fields; + private readonly Dictionary _fields; protected internal WorkItemCore() :this(null) { } - protected internal WorkItemCore([CanBeNull] IDictionary fields) + protected internal WorkItemCore([CanBeNull] Dictionary fields) { _fields = fields ?? new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -53,12 +54,26 @@ public virtual object this[string name] protected virtual T GetValue(string name) { - return TypeParser.Default.Parse(GetValue(name)); + var value = GetValue(name); + + if (value == null) + { + if (typeof(T) == typeof(string)) + { + return (T)(object)string.Empty; + } + } + + return TypeParser.Default.Parse(value, default(T)); } - protected virtual object GetValue(string name) + [CanBeNull] + [JetBrains.Annotations.Pure] + protected virtual object GetValue([NotNull] string name) { - return _fields != null && _fields.TryGetValue(name, out object val) ? val : null; + Contract.Requires(!string.IsNullOrEmpty(name)); + + return _fields.TryGetValue(name, out object val) ? val : null; } protected virtual void SetValue(string name, object value) From ca50c62983397245c2a971023e463b4d6b6864b0 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 28 Apr 2017 16:13:04 -0700 Subject: [PATCH 184/251] Update Query.cs - Add JetBrains and contract assertions - Force materialization of return value of `RunLinkQuery` - Reduce memory allocations for paths for link queries and WIQL queries - Remaining allocations documented in comments --- src/Qwiq.Core.Rest/Query.cs | 129 ++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 1c908fde..967b050c 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -32,21 +32,23 @@ internal class Query : IQuery private readonly WorkItemStore _workItemStore; - private IEnumerable _ids; + [CanBeNull] + private HashSet _ids; - internal Query(IEnumerable ids, Wiql query, [NotNull] WorkItemStore workItemStore) + internal Query([NotNull] IEnumerable ids, Wiql query, [NotNull] WorkItemStore workItemStore) : this(query, false, workItemStore) { + if (ids == null) throw new ArgumentNullException(nameof(ids)); Contract.Requires(workItemStore != null); - - _ids = ids; + + _ids = new HashSet(ids); } internal Query(Wiql query, bool timePrecision, [NotNull] WorkItemStore workItemStore) { Contract.Requires(workItemStore != null); - + _workItemStore = workItemStore ?? throw new ArgumentNullException(nameof(workItemStore)); _timePrecision = timePrecision; _query = query; @@ -67,77 +69,118 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() [ItemNotNull] public IEnumerable RunLinkQuery() + { + return RunkLinkQueryImpl().ToList().AsReadOnly(); + } + + public IWorkItemCollection RunQuery() + { + if (_ids == null && _query == null) throw new InvalidOperationException(); + + // Allocate for method iterator and WorkItemCollection object + return new WorkItemCollection(RunQueryImpl().ToList()); + } + + private static DateTime? ExtractAsOf(string wiql) + { + var m = AsOfRegex.Match(wiql); + if (!m.Success) return null; + + if (!DateTime.TryParse(m.Groups["date"].Value, out DateTime retval)) throw new Exception(); + + return retval; + } + + private IWorkItemLinkType LinkFunc(string s) + { + return _workItemStore.WorkItemLinkTypes[s]; + } + + private IEnumerable RunkLinkQueryImpl() { // Eager loading for the link type ID (which is not returned by the REST API) causes ~250ms delay - var ends = new Lazy(() => (WorkItemLinkTypeEndCollection)GetLinkTypes()); + + // REVIEW: Closure variable "ends" allocates, preventing local cache + // REVIEW: Delegate for ctor of Lazy also allocates + var ends = new Lazy(WorkItemLinkTypeEndValueFactory); var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); - foreach (var workItemLink in result.WorkItemRelations) + // To avoid an enumerator allocation we are forcing the cast + for (var index = 0; index < ((List)result.WorkItemRelations).Count; index++) { + // REVIEW: Closure allocation: workItemLink + ends outer closure + var workItemLink = ((List)result.WorkItemRelations)[index]; + + IWorkItemLinkTypeEnd EndValueFactory() + { + return ends.Value.TryGetByName(workItemLink.Rel, out IWorkItemLinkTypeEnd end) ? end : null; + } + yield return new WorkItemLinkInfo( workItemLink.Source?.Id ?? 0, workItemLink.Target?.Id ?? 0, - new Lazy( - () => ends.Value.TryGetByName( - workItemLink.Rel, - out IWorkItemLinkTypeEnd end) - ? end - : null)); + new Lazy(EndValueFactory)); } } - public IWorkItemCollection RunQuery() - { - if (_ids == null && _query == null) throw new InvalidOperationException(); - - return new WorkItemCollection(RunQueryImpl().ToList()); - } - [NotNull] private IEnumerable RunQueryImpl() { Contract.Ensures(Contract.Result>() != null); - + if (_ids == null && _query != null) { var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); if (!result.WorkItems.Any()) yield break; - _ids = result.WorkItems.Select(wir => wir.Id); + _ids = new HashSet(); + // REVIEW: Possible iterator allocation + foreach (var wir in result.WorkItems) + { + _ids.Add(wir.Id); + } } if (_ids == null) yield break; - var expand = WorkItemExpand.All; + const WorkItemExpand Expand = WorkItemExpand.All; var qry = _ids.Partition(_workItemStore.PageSize); - var ts = qry.Select(s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, null, _asOf, expand, WorkItemErrorPolicy.Omit)); + var ts = qry.Select( + s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync( + s, + null, + _asOf, + Expand, + WorkItemErrorPolicy.Omit)); // This is done in parallel so keep performance similar to the SOAP client - foreach (var workItem in Task.WhenAll(ts).GetAwaiter().GetResult().SelectMany(s => s.Select(f => f))) + foreach (var workItemsPartition in Task.WhenAll(ts).GetAwaiter().GetResult()) { - IWorkItemType WorkItemTypeFactory() + // REIVEW: Allocate for workItem variable + foreach (var workItem in workItemsPartition) { - // REST API does not return the WIT with the item - // Eagerly loading requires several trips to the server at a cost of 50-2500ms for each trip - var proj = (string)workItem.Fields[CoreFieldRefNames.TeamProject]; - var witName = (string)workItem.Fields[CoreFieldRefNames.WorkItemType]; - return _workItemStore.Projects[proj].WorkItemTypes[witName]; + IWorkItemType WorkItemTypeFactory() + { + // REST API does not return the WIT with the item + // Eagerly loading requires several trips to the server at a cost of 50-2500ms for each trip + var proj = (string)workItem.Fields[CoreFieldRefNames.TeamProject]; + var witName = (string)workItem.Fields[CoreFieldRefNames.WorkItemType]; + return _workItemStore.Projects[proj].WorkItemTypes[witName]; + } + + // REIVEW: Allocate for WorkItem reference type + yield return new WorkItem( + workItem, + // REVIEW: Allocate for reference type + new Lazy(WorkItemTypeFactory), + // REVIEW: Delegate allocation from method group + LinkFunc).AsProxy(); } - - yield return new WorkItem( - workItem, - new Lazy(WorkItemTypeFactory), - s => _workItemStore.WorkItemLinkTypes[s]).AsProxy(); } } - private static DateTime? ExtractAsOf(string wiql) + private WorkItemLinkTypeEndCollection WorkItemLinkTypeEndValueFactory() { - var m = AsOfRegex.Match(wiql); - if (!m.Success) return null; - - if (!DateTime.TryParse(m.Groups["date"].Value, out DateTime retval)) throw new Exception(); - - return retval; + return (WorkItemLinkTypeEndCollection)GetLinkTypes(); } } } \ No newline at end of file From a906a2e6a02e12f8456938cc54206464854c65e0 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 1 May 2017 15:15:46 -0700 Subject: [PATCH 185/251] Reduce memory allocations for exception proxies --- .../Exceptions/AggregateExceptionExploder.cs | 16 ++++++-- .../ExceptionHandlingDynamicProxyFactory.cs | 26 ++++++++----- src/Qwiq.Core/Exceptions/ExceptionMapper.cs | 39 +++++++++++++------ .../Exceptions/IExceptionExploder.cs | 7 +++- src/Qwiq.Core/Exceptions/IExceptionMapper.cs | 3 ++ .../Exceptions/InnerExceptionExploder.cs | 20 ++++++---- src/Qwiq.Core/Properties/AssemblyInfo.cs | 1 + .../ExceptionHandlingDynamicProxyTests.cs | 5 +-- .../Exceptions/ExceptionMapperTests.cs | 10 ++--- 9 files changed, 85 insertions(+), 42 deletions(-) diff --git a/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs b/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs index 57f4b15b..8a656bfd 100644 --- a/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs +++ b/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs @@ -1,15 +1,23 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace Microsoft.Qwiq.Exceptions { internal class AggregateExceptionExploder : IExceptionExploder { - public IEnumerable Explode(Exception exception) + private static readonly ReadOnlyCollection Empty = new ReadOnlyCollection(new List()); + + public ReadOnlyCollection Explode(Exception exception) { - return new [] {exception}.OfType().Select(ae => ae.Flatten()).SelectMany(ae => ae.InnerExceptions); + if (exception is AggregateException ae) + { + var ae1 = ae.Flatten(); + return ae1.InnerExceptions; + } + + return Empty; } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs index ca4ef630..3168e22f 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxyFactory.cs @@ -1,37 +1,45 @@ using Castle.DynamicProxy; using JetBrains.Annotations; -using System.Collections.Generic; + using System.Diagnostics.Contracts; namespace Microsoft.Qwiq.Exceptions { - public static class ExceptionHandlingDynamicProxyFactory + internal static class ExceptionHandlingDynamicProxyFactory { + [NotNull] private static readonly ProxyGenerator Generator = new ProxyGenerator(); + [NotNull] private static readonly ProxyGenerationOptions Options = new ProxyGenerationOptions { BaseTypeForInterfaceProxy = typeof(ProxyBase) }; + [NotNull] + private static readonly IExceptionExploder[] ExceptionExploders = { new AggregateExceptionExploder(), new InnerExceptionExploder() }; + + [NotNull] + private static readonly IExceptionMapper[] ExceptionMappers = { new InvalidOperationExceptionMapper(), new TransientExceptionMapper() }; + + [NotNull] + private static readonly ExceptionHandlingDynamicProxy Proxy = new ExceptionHandlingDynamicProxy(new ExceptionMapper(ExceptionExploders, ExceptionMappers)); + [JetBrains.Annotations.Pure] [NotNull] - public static T Create([NotNull] T instance) + internal static T Create([NotNull] T instance) where T : class { Contract.Requires(instance != null); Contract.Ensures(Contract.Result() != null); - return Create( - instance, - new IExceptionExploder[] { new AggregateExceptionExploder(), new InnerExceptionExploder() }, - new IExceptionMapper[] { new InvalidOperationExceptionMapper(), new TransientExceptionMapper() }); + return (T)Generator.CreateInterfaceProxyWithTarget(typeof(T), instance, Options, Proxy); } [NotNull] [JetBrains.Annotations.Pure] internal static T Create( [NotNull] T instance, - [NotNull] IEnumerable exploders, - [NotNull] IEnumerable mappers) + [NotNull] IExceptionExploder[] exploders, + [NotNull] IExceptionMapper[] mappers) where T : class { Contract.Requires(instance != null); diff --git a/src/Qwiq.Core/Exceptions/ExceptionMapper.cs b/src/Qwiq.Core/Exceptions/ExceptionMapper.cs index e6f1c50c..d7af6614 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionMapper.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionMapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Linq; using JetBrains.Annotations; @@ -9,10 +8,12 @@ namespace Microsoft.Qwiq.Exceptions { internal class ExceptionMapper : IExceptionMapper { - private readonly IEnumerable _exploders; - private readonly IEnumerable _mappers; + private readonly IExceptionExploder[] _exploders; + private readonly IExceptionMapper[] _mappers; - public ExceptionMapper([NotNull] IEnumerable exploders, [NotNull] IEnumerable mappers) + public ExceptionMapper( + [NotNull] IExceptionExploder[] exploders, + [NotNull] IExceptionMapper[] mappers) { Contract.Requires(exploders != null); Contract.Requires(mappers != null); @@ -21,7 +22,7 @@ public ExceptionMapper([NotNull] IEnumerable exploders, [Not _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); } - public Exception Map(Exception ex) + public Exception Map([CanBeNull] Exception ex) { return MapImpl(ex) ?? ex; } @@ -34,17 +35,31 @@ private Exception MapImpl(Exception ex) while (q.Count > 0) { var item = q.Dequeue(); - var mappedException = _mappers - .Select(mapper => mapper.Map(item)) - .FirstOrDefault(me => me != null); + Exception mappedException = null; + for (var i = 0; i < _mappers.Length; i++) + { + var mapper = _mappers[i]; + var me = mapper.Map(item); + if (me == null) continue; + mappedException = me; + break; + } if (mappedException == null) { - foreach (var childException in _exploders - .Select(exceptionExploder => exceptionExploder.Explode(item)) - .SelectMany(e => e)) + for (var i = 0; i < _exploders.Length; i++) { - q.Enqueue(childException); + var exceptionExploder = _exploders[i]; + var exceptions = exceptionExploder.Explode(item); + + if (exceptions == null) continue; + + + for (var j = 0; j < exceptions.Count; j++) + { + var childException = exceptions[j]; + q.Enqueue(childException); + } } } else diff --git a/src/Qwiq.Core/Exceptions/IExceptionExploder.cs b/src/Qwiq.Core/Exceptions/IExceptionExploder.cs index db9f6cd1..68b7f43d 100644 --- a/src/Qwiq.Core/Exceptions/IExceptionExploder.cs +++ b/src/Qwiq.Core/Exceptions/IExceptionExploder.cs @@ -1,11 +1,14 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; + +using JetBrains.Annotations; namespace Microsoft.Qwiq.Exceptions { public interface IExceptionExploder { - IEnumerable Explode(Exception exception); + [CanBeNull] + ReadOnlyCollection Explode([CanBeNull] Exception exception); } } diff --git a/src/Qwiq.Core/Exceptions/IExceptionMapper.cs b/src/Qwiq.Core/Exceptions/IExceptionMapper.cs index 06dcdbce..150432e8 100644 --- a/src/Qwiq.Core/Exceptions/IExceptionMapper.cs +++ b/src/Qwiq.Core/Exceptions/IExceptionMapper.cs @@ -1,9 +1,12 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Exceptions { public interface IExceptionMapper { + [CanBeNull] Exception Map(Exception ex); } } diff --git a/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs b/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs index 7f4a8ae4..8b28b963 100644 --- a/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs +++ b/src/Qwiq.Core/Exceptions/InnerExceptionExploder.cs @@ -1,19 +1,25 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; -using System.Linq; namespace Microsoft.Qwiq.Exceptions { [DebuggerStepThrough] internal class InnerExceptionExploder : IExceptionExploder { - public IEnumerable Explode(Exception exception) + private static readonly ReadOnlyCollection Empty = new ReadOnlyCollection(new List()); + + public ReadOnlyCollection Explode(Exception exception) { - if (exception == null) Enumerable.Empty(); - if (exception.InnerException != null) return new[] { exception.InnerException}; - return Enumerable.Empty(); + if (exception?.InnerException == null) return Empty; + + // REVIEW: Reference type object allocation is extraneous + var l = new List(1) + { + exception.InnerException + }; + return l.AsReadOnly(); } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/Properties/AssemblyInfo.cs b/src/Qwiq.Core/Properties/AssemblyInfo.cs index 6db99a08..3c52df59 100644 --- a/src/Qwiq.Core/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core/Properties/AssemblyInfo.cs @@ -19,3 +19,4 @@ [assembly: InternalsVisibleTo("Microsoft.Qwiq.Relatives.Tests")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.IntegrationTests")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.UnitTests")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Soap")] diff --git a/test/Qwiq.Core.Tests/Exceptions/ExceptionHandlingDynamicProxyTests.cs b/test/Qwiq.Core.Tests/Exceptions/ExceptionHandlingDynamicProxyTests.cs index 26164395..c1b8ef3d 100644 --- a/test/Qwiq.Core.Tests/Exceptions/ExceptionHandlingDynamicProxyTests.cs +++ b/test/Qwiq.Core.Tests/Exceptions/ExceptionHandlingDynamicProxyTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Microsoft.Qwiq.Tests.Common; using Microsoft.Qwiq.UnitTests.Mocks; @@ -18,8 +17,8 @@ public override void Given() ProxiedInstance = ExceptionHandlingDynamicProxyFactory.Create( InstanceToProxy, - Enumerable.Empty(), - new[] + new IExceptionExploder[0], + new IExceptionMapper[] { new MockArgumentExceptionMapper() }); diff --git a/test/Qwiq.Core.Tests/Exceptions/ExceptionMapperTests.cs b/test/Qwiq.Core.Tests/Exceptions/ExceptionMapperTests.cs index a14df15d..352713f7 100644 --- a/test/Qwiq.Core.Tests/Exceptions/ExceptionMapperTests.cs +++ b/test/Qwiq.Core.Tests/Exceptions/ExceptionMapperTests.cs @@ -15,8 +15,8 @@ namespace Microsoft.Qwiq.Exceptions public class ExceptionMapperTests : ContextSpecification { protected IExceptionMapper ExceptionMapper { get; set; } - protected IEnumerable ExceptionExploders { get; set; } - protected IEnumerable ExceptionMappers { get; set; } + protected IExceptionExploder[] ExceptionExploders { get; set; } + protected IExceptionMapper[] ExceptionMappers { get; set; } protected Exception Input { get; set; } protected Exception ActualResult { get; set; } @@ -36,7 +36,7 @@ public class given_an_exception_with_a_supported_mapper : ExceptionMapperTests { public override void Given() { - ExceptionExploders = Enumerable.Empty(); + ExceptionExploders = new IExceptionExploder[0]; ExceptionMappers = new[] {new MockArgumentExceptionMapper()}; Input = new ArgumentException(null, MockArgumentExceptionMapper.MockParamName); base.Given(); @@ -54,7 +54,7 @@ public class given_an_exception_without_a_supported_mapper : ExceptionMapperTest { public override void Given() { - ExceptionExploders = Enumerable.Empty(); + ExceptionExploders = new IExceptionExploder[0]; ExceptionMappers = new[] { new MockArgumentExceptionMapper() }; Input = new ArgumentNullException(); base.Given(); @@ -94,7 +94,7 @@ public void the_argument_mapper_should_be_called_three_times() public override void Given() { - ExceptionExploders = Enumerable.Empty(); + ExceptionExploders = new IExceptionExploder[0]; ExceptionMappers = new[] {new MockVssExceptionMapper(HandledErrorCodes.ToArray())}; base.Given(); } From c61ad76a2a5f153b96d47febb1f2565f5584246d Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 1 May 2017 15:18:15 -0700 Subject: [PATCH 186/251] Reduce memory allocations in query critical path --- src/Qwiq.Core.Rest/LinkCollection.cs | 13 +++- src/Qwiq.Core.Rest/Query.cs | 90 +++++++++++++++----------- src/Qwiq.Core.Rest/WorkItem.cs | 66 +++++++++++++------ src/Qwiq.Core.Soap/Query.cs | 14 ++-- src/Qwiq.Core/IEnumerableExtensions.cs | 2 +- src/Qwiq.Core/WorkItem.cs | 43 ++++++++++-- src/Qwiq.Core/WorkItemCore.cs | 3 +- test/Qwiq.Mocks/MockWorkItem.cs | 49 ++++++++------ 8 files changed, 187 insertions(+), 93 deletions(-) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index d5087124..1511a74d 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -1,21 +1,28 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Client.Rest { internal class LinkCollection : ReadOnlyObjectWithNameCollection, ICollection { - public LinkCollection(IEnumerable relations, Func linkFunc) + public LinkCollection([CanBeNull] List relations, [NotNull] Func linkFunc) { - if (relations == null) throw new ArgumentNullException(nameof(relations)); + + Contract.Requires(linkFunc != null); + + if (relations == null) return; if (linkFunc == null) throw new ArgumentNullException(nameof(linkFunc)); - foreach (var relation in relations) + for (var i = 0; i < relations.Count; i++) { + var relation = relations[i]; if (CoreLinkTypeReferenceNames.Related.Equals(relation.Rel, StringComparison.OrdinalIgnoreCase)) { // Last part of the Url is the ID diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 967b050c..5930ec5c 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Linq; using System.Text.RegularExpressions; @@ -13,6 +14,9 @@ namespace Microsoft.Qwiq.Client.Rest { internal class Query : IQuery { + // TODO: Make this configurable + private const WorkItemExpand Expand = WorkItemExpand.All; + internal const int MaximumBatchSize = 200; internal const int MaximumFieldSize = 100; @@ -24,12 +28,15 @@ internal class Query : IQuery @"(?asof\s)\'(?.*)\'", RegexOptions.IgnoreCase | RegexOptions.Singleline); + private static readonly ReadOnlyCollection EmptyWorkItems = new ReadOnlyCollection(new List()); + private readonly DateTime? _asOf; private readonly Wiql _query; private readonly bool _timePrecision; + [NotNull] private readonly WorkItemStore _workItemStore; [CanBeNull] @@ -70,7 +77,7 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() [ItemNotNull] public IEnumerable RunLinkQuery() { - return RunkLinkQueryImpl().ToList().AsReadOnly(); + return RunkLinkQueryImpl(); } public IWorkItemCollection RunQuery() @@ -78,7 +85,7 @@ public IWorkItemCollection RunQuery() if (_ids == null && _query == null) throw new InvalidOperationException(); // Allocate for method iterator and WorkItemCollection object - return new WorkItemCollection(RunQueryImpl().ToList()); + return new WorkItemCollection(RunQueryImpl()); } private static DateTime? ExtractAsOf(string wiql) @@ -96,7 +103,7 @@ private IWorkItemLinkType LinkFunc(string s) return _workItemStore.WorkItemLinkTypes[s]; } - private IEnumerable RunkLinkQueryImpl() + private ReadOnlyCollection RunkLinkQueryImpl() { // Eager loading for the link type ID (which is not returned by the REST API) causes ~250ms delay @@ -106,76 +113,87 @@ private IEnumerable RunkLinkQueryImpl() var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); // To avoid an enumerator allocation we are forcing the cast - for (var index = 0; index < ((List)result.WorkItemRelations).Count; index++) + var result2 = (List)result.WorkItemRelations; + var retval = new List(result2.Count); + + for (var index = 0; index < result2.Count; index++) { // REVIEW: Closure allocation: workItemLink + ends outer closure - var workItemLink = ((List)result.WorkItemRelations)[index]; + var workItemLink = result2[index]; IWorkItemLinkTypeEnd EndValueFactory() { return ends.Value.TryGetByName(workItemLink.Rel, out IWorkItemLinkTypeEnd end) ? end : null; } - yield return new WorkItemLinkInfo( + retval.Add(new WorkItemLinkInfo( workItemLink.Source?.Id ?? 0, workItemLink.Target?.Id ?? 0, - new Lazy(EndValueFactory)); + new Lazy(EndValueFactory))); } + + return retval.AsReadOnly(); } [NotNull] - private IEnumerable RunQueryImpl() + private ReadOnlyCollection RunQueryImpl() { Contract.Ensures(Contract.Result>() != null); if (_ids == null && _query != null) { var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); - if (!result.WorkItems.Any()) yield break; + if (!result.WorkItems.Any()) return EmptyWorkItems; _ids = new HashSet(); - // REVIEW: Possible iterator allocation - foreach (var wir in result.WorkItems) + var items = (List)result.WorkItems; + for (var i = 0; i < items.Count; i++) { + var wir = items[i]; _ids.Add(wir.Id); } } - if (_ids == null) yield break; + if (_ids == null) return EmptyWorkItems; + + var retval = new List(_ids.Count); - const WorkItemExpand Expand = WorkItemExpand.All; + var ts = + new List>>( + (_ids.Count % _workItemStore.PageSize) + + 1); var qry = _ids.Partition(_workItemStore.PageSize); - var ts = qry.Select( - s => _workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync( - s, - null, - _asOf, - Expand, - WorkItemErrorPolicy.Omit)); + foreach (var s in qry) + { + ts.Add(_workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, null, _asOf, Expand, WorkItemErrorPolicy.Omit)); + } + + var results = Task.WhenAll(ts).GetAwaiter().GetResult(); + // This is done in parallel so keep performance similar to the SOAP client - foreach (var workItemsPartition in Task.WhenAll(ts).GetAwaiter().GetResult()) + for (var i = 0; i < results.Length; i++) { + var workItemsPartition = results[i]; // REIVEW: Allocate for workItem variable - foreach (var workItem in workItemsPartition) + for (var j = 0; j < workItemsPartition.Count; j++) { - IWorkItemType WorkItemTypeFactory() - { - // REST API does not return the WIT with the item - // Eagerly loading requires several trips to the server at a cost of 50-2500ms for each trip - var proj = (string)workItem.Fields[CoreFieldRefNames.TeamProject]; - var witName = (string)workItem.Fields[CoreFieldRefNames.WorkItemType]; - return _workItemStore.Projects[proj].WorkItemTypes[witName]; - } + var workItem = workItemsPartition[j]; // REIVEW: Allocate for WorkItem reference type - yield return new WorkItem( - workItem, - // REVIEW: Allocate for reference type - new Lazy(WorkItemTypeFactory), - // REVIEW: Delegate allocation from method group - LinkFunc).AsProxy(); + var wi = new WorkItem( + workItem, + // REVIEW: Allocate for reference type + _workItemStore + .Projects[(string)workItem.Fields[CoreFieldRefNames.TeamProject]] + .WorkItemTypes[(string)workItem.Fields[CoreFieldRefNames.WorkItemType]], + // REVIEW: Delegate allocation from method group + LinkFunc).AsProxy(); + + retval.Add(wi); } } + + return retval.AsReadOnly(); } private WorkItemLinkTypeEndCollection WorkItemLinkTypeEndValueFactory() diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index 7c4a0027..a9cf1339 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -1,34 +1,53 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; +using Microsoft.VisualStudio.Services.Common; + namespace Microsoft.Qwiq.Client.Rest { internal class WorkItem : Qwiq.WorkItem { - private readonly Lazy _fields; + private readonly Func _linkFunc; private readonly TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem _item; - private readonly Lazy> _links; - private readonly Lazy _wit; - internal WorkItem( - TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, - Lazy wit, - Func linkFunc) - : base((Dictionary)item.Fields) + [CanBeNull] + private LinkCollection _links2; + + + + + [CanBeNull] + private IFieldCollection _fields2; + + public WorkItem( + [NotNull] TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, + [NotNull] IWorkItemType wit, + Func linkFunc + ) + : base(wit) { - _item = item; - _wit = wit; - Uri = new Uri(item.Url); - Url = item.Url; - _fields = new Lazy(() => new FieldCollection(this, Type.FieldDefinitions, (r, d) => new Field(r, d))); - _links = new Lazy>(() => new LinkCollection(item.Relations, linkFunc)); + Contract.Requires(item != null); + Contract.Requires(wit != null); + _item = item ?? throw new ArgumentNullException(nameof(item)); + _linkFunc = linkFunc; + + + + + } - public override IFieldCollection Fields => _fields.Value; + + + public override IFieldCollection Fields => _fields2 ?? (_fields2 = new FieldCollection(this, Type.FieldDefinitions, (r, d) => new Field(r, d))); public override int Id => _item.Id.GetValueOrDefault(0); @@ -38,7 +57,8 @@ public override string Keywords set => SetValue(WorkItemFields.Keywords, value); } - public override ICollection Links => _links.Value; + public override ICollection Links => _links2 + ?? (_links2 = new LinkCollection((List)_item.Relations, _linkFunc)); /// public override int RelatedLinkCount @@ -103,10 +123,20 @@ public override int AttachedFileCount public override int Rev => _item.Rev.GetValueOrDefault(0); - public override IWorkItemType Type => _wit.Value; - public override Uri Uri { get; } public override string Url { get; } + + protected override object GetValue(string name) + { + //return _item.Fields[name]; + _item.Fields.TryGetValue(name, out object value); + return value; + } + + protected override void SetValue(string name, object value) + { + _item.Fields[name] = value; + } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index 582ac73f..bec75c77 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -59,12 +59,14 @@ public IWorkItemCollection RunQuery() var wic = _query.RunQuery(); wic.PageSize = _pageSize; - return - wic - .Cast() - .Select(item => ExceptionHandlingDynamicProxyFactory.Create((WorkItem)item)) - .ToList() - .ToWorkItemCollection(); + var items = new List(wic.Count); + for (var i = 0; i < wic.Count; i++) + { + var item = ExceptionHandlingDynamicProxyFactory.Create((WorkItem)wic[i]); + items.Add(item); + } + + return new WorkItemCollection(items); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IEnumerableExtensions.cs b/src/Qwiq.Core/IEnumerableExtensions.cs index a9d18a33..a942d239 100644 --- a/src/Qwiq.Core/IEnumerableExtensions.cs +++ b/src/Qwiq.Core/IEnumerableExtensions.cs @@ -6,7 +6,7 @@ namespace System.Collections.Generic internal static class IEnumerableExtensions // ReSharper restore InconsistentNaming { - public static IEnumerable> Partition(this IEnumerable source, int size) + public static IEnumerable Partition(this HashSet source, int size) { var count = 0; T[] group = null; // use arrays as buffer diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index 9a6701bd..c963e502 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -17,11 +17,17 @@ public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable _fields; + private IFieldCollection _fields; - protected internal WorkItem(Dictionary fields) + [CanBeNull] + private Func _fieldFactory; + + protected internal WorkItem([NotNull] IWorkItemType type, [CanBeNull] Dictionary fields) : base(fields) { + Contract.Requires(type != null); + + _type = type ?? throw new ArgumentNullException(nameof(type)); } protected internal WorkItem([NotNull] IWorkItemType type) @@ -29,15 +35,16 @@ protected internal WorkItem([NotNull] IWorkItemType type) Contract.Requires(type != null); _type = type ?? throw new ArgumentNullException(nameof(type)); - _fields = new Lazy(()=> new FieldCollection(this, Type.FieldDefinitions, (revision, definition) => new Field(revision, definition))); + } - protected internal WorkItem([NotNull] IWorkItemType type, Func fieldCollectionFactory) + protected internal WorkItem([NotNull] IWorkItemType type, [NotNull] Func fieldCollectionFactory) { - Contract.Requires(type != null); + Contract.Requires(type != null); + Contract.Requires(fieldCollectionFactory != null); _type = type ?? throw new ArgumentNullException(nameof(type)); - _fields = new Lazy(fieldCollectionFactory); + _fieldFactory = fieldCollectionFactory ?? throw new ArgumentNullException(nameof(fieldCollectionFactory)); } public virtual int Revision => Rev; @@ -57,7 +64,29 @@ public bool Equals(IWorkItem other) public new virtual int ExternalLinkCount => base.ExternalLinkCount.GetValueOrDefault(0); - public virtual IFieldCollection Fields => _fields == null ? throw new NotSupportedException() : _fields.Value; + public virtual IFieldCollection Fields + { + get + { + if (_fields != null) return _fields; + + if (_fieldFactory != null) + { + _fields = _fieldFactory(); + _fieldFactory = null; + } + else + { + _fields = new FieldCollection( + this, + Type.FieldDefinitions, + (revision, definition) => new Field(revision, definition)); + } + + return _fields; + + } + } public new virtual int HyperlinkCount => base.HyperlinkCount.GetValueOrDefault(0); diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index 639bba2c..0fbbf25e 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -11,7 +11,6 @@ public abstract class WorkItemCore : IWorkItemCore, IEquatable, I private readonly Dictionary _fields; protected internal WorkItemCore() - :this(null) { } @@ -72,7 +71,7 @@ protected virtual T GetValue(string name) protected virtual object GetValue([NotNull] string name) { Contract.Requires(!string.IsNullOrEmpty(name)); - + return _fields.TryGetValue(name, out object val) ? val : null; } diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index c1bbdeaf..997e2fe2 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Mocks { [Serializable] @@ -47,42 +50,33 @@ public MockWorkItem(string workItemType = null, IDictionary fiel CoreFieldDefinitions.All.Union( fields?.Keys.Select(MockFieldDefinition.Create) ?? Enumerable.Empty())), - fields) + (Dictionary)fields) { } - public MockWorkItem(IWorkItemType type, int id) + public MockWorkItem([NotNull] IWorkItemType type, int id) : this(type, new KeyValuePair(CoreFieldRefNames.Id, id)) { + Contract.Requires(id > 0); } - public MockWorkItem(IWorkItemType type, int id, params KeyValuePair[] fieldValues) + public MockWorkItem([NotNull] IWorkItemType type, int id, [CanBeNull] params KeyValuePair[] fieldValues) : this( type, - fieldValues == null - ? new Dictionary(StringComparer.OrdinalIgnoreCase) { { CoreFieldRefNames.Id, id } } - : fieldValues.Union(new[] { new KeyValuePair(CoreFieldRefNames.Id, id) }) - .ToDictionary(k => k.Key, e => e.Value, StringComparer.OrdinalIgnoreCase)) + fieldValues?.Union(new[] { new KeyValuePair(CoreFieldRefNames.Id, id) }) + .ToDictionary(k => k.Key, e => e.Value, StringComparer.OrdinalIgnoreCase) ?? new Dictionary(StringComparer.OrdinalIgnoreCase) { { CoreFieldRefNames.Id, id } }) { + Contract.Requires(id > 0); } - public MockWorkItem(IWorkItemType type, params KeyValuePair[] fieldValues) - : this(type, fieldValues == null ? null : fieldValues.ToDictionary(k => k.Key, e => e.Value, StringComparer.OrdinalIgnoreCase)) + public MockWorkItem([NotNull] IWorkItemType type, [CanBeNull] params KeyValuePair[] fieldValues) + : this(type, fieldValues?.ToDictionary(k => k.Key, e => e.Value, StringComparer.OrdinalIgnoreCase)) { } - public MockWorkItem(IWorkItemType type, IDictionary fields = null) - : base(type) + public MockWorkItem([NotNull] IWorkItemType type, [CanBeNull] Dictionary fields = null) + : base(type, NormalizeFields(type, fields)) { - // set any values coming into - if (fields != null) - { - foreach (var field in fields) - { - Fields[field.Key].Value = field.Value; - } - } - SetFieldValue(type.FieldDefinitions[CoreFieldRefNames.WorkItemType], type.Name); SetFieldValue(type.FieldDefinitions[CoreFieldRefNames.RevisedDate], new DateTime(9999, 1, 1, 0, 0, 0)); @@ -91,6 +85,21 @@ public MockWorkItem(IWorkItemType type, IDictionary fields = nul ApplyRules(); } + private static Dictionary NormalizeFields(IWorkItemType type, Dictionary fields) + { + if (fields == null) return null; + var retval = new Dictionary(fields.Comparer); + + foreach (var field in fields) + { + var fieldDef = type.FieldDefinitions[field.Key]; + retval.Add(fieldDef.ReferenceName, field.Value); + + } + + return retval; + } + public override IRelatedLink CreateRelatedLink(IWorkItemLinkTypeEnd linkTypeEnd, IWorkItem relatedWorkItem) { return CreateRelatedLink(relatedWorkItem.Id, linkTypeEnd); From 7e966d142569226f5c78a320fa416c1f7568fd90 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 1 May 2017 15:23:52 -0700 Subject: [PATCH 187/251] Code clean up --- src/Qwiq.Core.Rest/Query.cs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 5930ec5c..1ef1083e 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -14,9 +14,6 @@ namespace Microsoft.Qwiq.Client.Rest { internal class Query : IQuery { - // TODO: Make this configurable - private const WorkItemExpand Expand = WorkItemExpand.All; - internal const int MaximumBatchSize = 200; internal const int MaximumFieldSize = 100; @@ -24,6 +21,9 @@ internal class Query : IQuery // This is defined in Microsoft.TeamFoundation.WorkItemTracking.Client.PageSizes internal const int MinimumBatchSize = 50; + // TODO: Make this configurable + private const WorkItemExpand Expand = WorkItemExpand.All; + private static readonly Regex AsOfRegex = new Regex( @"(?asof\s)\'(?.*)\'", RegexOptions.IgnoreCase | RegexOptions.Singleline); @@ -126,10 +126,11 @@ IWorkItemLinkTypeEnd EndValueFactory() return ends.Value.TryGetByName(workItemLink.Rel, out IWorkItemLinkTypeEnd end) ? end : null; } - retval.Add(new WorkItemLinkInfo( - workItemLink.Source?.Id ?? 0, - workItemLink.Target?.Id ?? 0, - new Lazy(EndValueFactory))); + retval.Add( + new WorkItemLinkInfo( + workItemLink.Source?.Id ?? 0, + workItemLink.Target?.Id ?? 0, + new Lazy(EndValueFactory))); } return retval.AsReadOnly(); @@ -157,19 +158,13 @@ private ReadOnlyCollection RunQueryImpl() var retval = new List(_ids.Count); - var ts = - new List>>( - (_ids.Count % _workItemStore.PageSize) - + 1); + var ts = new List>>(_ids.Count % _workItemStore.PageSize + 1); var qry = _ids.Partition(_workItemStore.PageSize); foreach (var s in qry) - { ts.Add(_workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, null, _asOf, Expand, WorkItemErrorPolicy.Omit)); - } var results = Task.WhenAll(ts).GetAwaiter().GetResult(); - // This is done in parallel so keep performance similar to the SOAP client for (var i = 0; i < results.Length; i++) { From 4ebd7d5823a3135d00259fa9f4e643f82f0fb776 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 1 May 2017 15:36:07 -0700 Subject: [PATCH 188/251] Update NuGet packages for Newtonsoft and Http Core --- src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj | 8 ++++---- src/Qwiq.Core.Rest/app.config | 12 +++++++++-- src/Qwiq.Core.Rest/packages.config | 4 ++-- src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj | 20 +++++++++---------- src/Qwiq.Core.Soap/app.config | 12 +++++++++-- src/Qwiq.Core.Soap/packages.config | 8 ++++---- src/Qwiq.Core/Qwiq.Core.csproj | 16 +++++++-------- src/Qwiq.Core/app.config | 10 +++++++--- src/Qwiq.Core/packages.config | 6 +++--- .../Qwiq.Identity.Soap.csproj | 20 +++++++++---------- src/Qwiq.Identity.Soap/app.config | 14 ++++++++++++- src/Qwiq.Identity.Soap/packages.config | 8 ++++---- src/Qwiq.Identity/Qwiq.Identity.csproj | 8 ++++---- src/Qwiq.Identity/app.config | 12 +++++++++-- src/Qwiq.Identity/packages.config | 4 ++-- src/Qwiq.Linq.Identity/app.config | 14 ++++++++++++- src/Qwiq.Linq/app.config | 12 +++++++++-- src/Qwiq.Mapper.Identity/app.config | 14 ++++++++++++- src/Qwiq.Mapper/app.config | 12 +++++++++-- .../Qwiq.Core.UnitTests.csproj | 20 +++++++++---------- test/Qwiq.Core.Tests/app.config | 14 ++++++++++++- test/Qwiq.Core.Tests/packages.config | 8 ++++---- test/Qwiq.Identity.Benchmark.Tests/app.config | 12 +++++++++-- .../Qwiq.Identity.UnitTests.csproj | 20 +++++++++---------- test/Qwiq.Identity.Tests/app.config | 12 +++++++++-- test/Qwiq.Identity.Tests/packages.config | 8 ++++---- .../Properties/AssemblyInfo.cs | 2 ++ .../Qwiq.IntegrationTests.csproj | 20 +++++++++---------- .../LargeWiqlHierarchyQueryTests.cs | 13 +++++++----- test/Qwiq.Integration.Tests/app.config | 14 ++++++++++++- test/Qwiq.Integration.Tests/packages.config | 8 ++++---- test/Qwiq.Linq.Tests/app.config | 12 +++++++++-- .../Qwiq.Mapper.BenchmarkTests.csproj | 4 ++-- test/Qwiq.Mapper.Benchmark.Tests/app.config | 14 ++++++++++++- .../packages.config | 2 +- .../Qwiq.Mapper.UnitTests.csproj | 8 ++++---- test/Qwiq.Mapper.Tests/app.config | 12 +++++++++-- test/Qwiq.Mapper.Tests/packages.config | 4 ++-- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 8 ++++---- test/Qwiq.Mocks/app.config | 12 +++++++++-- test/Qwiq.Mocks/packages.config | 4 ++-- 41 files changed, 303 insertions(+), 142 deletions(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj index 5a8a8059..30270d62 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj @@ -57,15 +57,15 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll diff --git a/src/Qwiq.Core.Rest/app.config b/src/Qwiq.Core.Rest/app.config index a75873c9..0ff3cf26 100644 --- a/src/Qwiq.Core.Rest/app.config +++ b/src/Qwiq.Core.Rest/app.config @@ -2,9 +2,17 @@ + + + + + + + + - + @@ -12,7 +20,7 @@ - + diff --git a/src/Qwiq.Core.Rest/packages.config b/src/Qwiq.Core.Rest/packages.config index 12f596c1..44048c8d 100644 --- a/src/Qwiq.Core.Rest/packages.config +++ b/src/Qwiq.Core.Rest/packages.config @@ -1,11 +1,11 @@  - + - + \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index 0f70507e..b015eed8 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -15,11 +15,11 @@ - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -143,8 +143,8 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll @@ -153,8 +153,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -162,8 +162,8 @@ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll diff --git a/src/Qwiq.Core.Soap/app.config b/src/Qwiq.Core.Soap/app.config index d7448256..0add16d2 100644 --- a/src/Qwiq.Core.Soap/app.config +++ b/src/Qwiq.Core.Soap/app.config @@ -8,11 +8,19 @@ - + - + + + + + + + + + diff --git a/src/Qwiq.Core.Soap/packages.config b/src/Qwiq.Core.Soap/packages.config index 9605a2e3..1f6d3630 100644 --- a/src/Qwiq.Core.Soap/packages.config +++ b/src/Qwiq.Core.Soap/packages.config @@ -1,9 +1,9 @@  - - - + + + @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 9a407aab..2d18acb2 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -20,11 +20,11 @@ ..\..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -41,8 +41,8 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll @@ -51,8 +51,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll diff --git a/src/Qwiq.Core/app.config b/src/Qwiq.Core/app.config index 348b5314..53d9b971 100644 --- a/src/Qwiq.Core/app.config +++ b/src/Qwiq.Core/app.config @@ -4,11 +4,11 @@ - + - + @@ -16,7 +16,11 @@ - + + + + + diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index d1bc9b15..a2e10a12 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -2,12 +2,12 @@ - - + + - + \ No newline at end of file diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj index 879469b4..9413f743 100644 --- a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj @@ -15,11 +15,11 @@ - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -143,8 +143,8 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll @@ -153,8 +153,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -162,8 +162,8 @@ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll diff --git a/src/Qwiq.Identity.Soap/app.config b/src/Qwiq.Identity.Soap/app.config index ab4357fc..f651787c 100644 --- a/src/Qwiq.Identity.Soap/app.config +++ b/src/Qwiq.Identity.Soap/app.config @@ -4,12 +4,24 @@ - + + + + + + + + + + + + + diff --git a/src/Qwiq.Identity.Soap/packages.config b/src/Qwiq.Identity.Soap/packages.config index 9605a2e3..1f6d3630 100644 --- a/src/Qwiq.Identity.Soap/packages.config +++ b/src/Qwiq.Identity.Soap/packages.config @@ -1,9 +1,9 @@  - - - + + + @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 3e6e5061..2cd162cb 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -48,14 +48,14 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll diff --git a/src/Qwiq.Identity/app.config b/src/Qwiq.Identity/app.config index c314ab62..53d9b971 100644 --- a/src/Qwiq.Identity/app.config +++ b/src/Qwiq.Identity/app.config @@ -4,16 +4,24 @@ - + - + + + + + + + + + diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index b3090d6a..d29d06c8 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -1,9 +1,9 @@  - + - + \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/app.config b/src/Qwiq.Linq.Identity/app.config index c83509f5..0ff3cf26 100644 --- a/src/Qwiq.Linq.Identity/app.config +++ b/src/Qwiq.Linq.Identity/app.config @@ -2,14 +2,26 @@ + + + + + + + + - + + + + + \ No newline at end of file diff --git a/src/Qwiq.Linq/app.config b/src/Qwiq.Linq/app.config index 556f3897..0ff3cf26 100644 --- a/src/Qwiq.Linq/app.config +++ b/src/Qwiq.Linq/app.config @@ -4,16 +4,24 @@ - + + + + + - + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/app.config b/src/Qwiq.Mapper.Identity/app.config index c83509f5..0ff3cf26 100644 --- a/src/Qwiq.Mapper.Identity/app.config +++ b/src/Qwiq.Mapper.Identity/app.config @@ -2,14 +2,26 @@ + + + + + + + + - + + + + + \ No newline at end of file diff --git a/src/Qwiq.Mapper/app.config b/src/Qwiq.Mapper/app.config index 556f3897..0ff3cf26 100644 --- a/src/Qwiq.Mapper/app.config +++ b/src/Qwiq.Mapper/app.config @@ -4,16 +4,24 @@ - + + + + + - + + + + + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj index 8d31473a..21bb271e 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj @@ -21,11 +21,11 @@ - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -155,8 +155,8 @@ ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll @@ -166,8 +166,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -175,8 +175,8 @@ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Core.Tests/app.config b/test/Qwiq.Core.Tests/app.config index ab4357fc..f651787c 100644 --- a/test/Qwiq.Core.Tests/app.config +++ b/test/Qwiq.Core.Tests/app.config @@ -4,12 +4,24 @@ - + + + + + + + + + + + + + diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index 4b9a9e20..6d48a844 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -1,9 +1,9 @@  - - - + + + @@ -13,7 +13,7 @@ - + diff --git a/test/Qwiq.Identity.Benchmark.Tests/app.config b/test/Qwiq.Identity.Benchmark.Tests/app.config index c8c0845a..62f2630c 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/app.config +++ b/test/Qwiq.Identity.Benchmark.Tests/app.config @@ -14,6 +14,14 @@ + + + + + + + + @@ -28,7 +36,7 @@ - + @@ -56,7 +64,7 @@ - + diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj index c5b60491..454cdbfe 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj @@ -21,11 +21,11 @@ - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -155,8 +155,8 @@ ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll @@ -166,8 +166,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -175,8 +175,8 @@ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Identity.Tests/app.config b/test/Qwiq.Identity.Tests/app.config index 43500b67..42bf2174 100644 --- a/test/Qwiq.Identity.Tests/app.config +++ b/test/Qwiq.Identity.Tests/app.config @@ -16,7 +16,7 @@ - + @@ -24,12 +24,20 @@ - + + + + + + + + + diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index 4b9a9e20..6d48a844 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -1,9 +1,9 @@  - - - + + + @@ -13,7 +13,7 @@ - + diff --git a/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs b/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs index 1edb3110..8cea0af5 100644 --- a/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs +++ b/test/Qwiq.Integration.Tests/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -8,3 +9,4 @@ [assembly: Guid("e4130432-c890-41e0-8407-c4142caf59d8")] +[assembly: InternalsVisibleTo("Qwiq.Test")] diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 7a263cae..78570295 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -24,11 +24,11 @@ ..\..\packages\JetBrains.dotMemoryUnit.2.3.20160517.113140\lib\dotMemory.Unit.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.9\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll ..\..\packages\WindowsAzure.ServiceBus.3.3.2\lib\net45-full\Microsoft.ServiceBus.dll @@ -158,8 +158,8 @@ ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll @@ -170,8 +170,8 @@ ..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.4.403061554\lib\net45\System.IdentityModel.Tokens.Jwt.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -179,8 +179,8 @@ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll True - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs index 71ad449e..d0e8a64a 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.dotMemoryUnit; +using JetBrains.dotMemoryUnit.Kernel; + using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -10,9 +13,7 @@ namespace Microsoft.Qwiq.WorkItemStore [TestClass] public class LargeWiqlHierarchyQueryTests : WorkItemStoreComparisonContextSpecification { - public override void When() - { - const string WIQL = @" + internal const string WIQL = @" SELECT [System.Id] FROM WorkItemLinks WHERE @@ -24,6 +25,8 @@ AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', "; + public override void When() + { RestResult.Links = TimedAction( () => RestResult.WorkItemStore.QueryLinks(WIQL).ToList(), "REST", @@ -33,8 +36,6 @@ AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', "REST", "Query - IDs"); - - SoapResult.Links = TimedAction( () => SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(), "SOAP", @@ -81,4 +82,6 @@ public void WorkItems_Equal() RestResult.WorkItems.ShouldContainOnly(SoapResult.WorkItems); } } + + } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/app.config b/test/Qwiq.Integration.Tests/app.config index ab4357fc..f651787c 100644 --- a/test/Qwiq.Integration.Tests/app.config +++ b/test/Qwiq.Integration.Tests/app.config @@ -4,12 +4,24 @@ - + + + + + + + + + + + + + diff --git a/test/Qwiq.Integration.Tests/packages.config b/test/Qwiq.Integration.Tests/packages.config index 26b222ee..6e737bc7 100644 --- a/test/Qwiq.Integration.Tests/packages.config +++ b/test/Qwiq.Integration.Tests/packages.config @@ -2,9 +2,9 @@ - - - + + + @@ -14,7 +14,7 @@ - + diff --git a/test/Qwiq.Linq.Tests/app.config b/test/Qwiq.Linq.Tests/app.config index 871b1c0f..129dc7e9 100644 --- a/test/Qwiq.Linq.Tests/app.config +++ b/test/Qwiq.Linq.Tests/app.config @@ -2,6 +2,14 @@ + + + + + + + + @@ -16,7 +24,7 @@ - + @@ -24,7 +32,7 @@ - + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj index 93867477..bcbc674d 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj @@ -52,8 +52,8 @@ ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll diff --git a/test/Qwiq.Mapper.Benchmark.Tests/app.config b/test/Qwiq.Mapper.Benchmark.Tests/app.config index 862d0363..720da180 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/app.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/app.config @@ -4,7 +4,7 @@ - + @@ -62,6 +62,18 @@ + + + + + + + + + + + + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 2591e89b..39a784ad 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -12,7 +12,7 @@ - + diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj index e9ca8384..d1a61a3b 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj @@ -36,16 +36,16 @@ ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\..\packages\Should.1.1.20\lib\Should.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll diff --git a/test/Qwiq.Mapper.Tests/app.config b/test/Qwiq.Mapper.Tests/app.config index 871b1c0f..129dc7e9 100644 --- a/test/Qwiq.Mapper.Tests/app.config +++ b/test/Qwiq.Mapper.Tests/app.config @@ -2,6 +2,14 @@ + + + + + + + + @@ -16,7 +24,7 @@ - + @@ -24,7 +32,7 @@ - + diff --git a/test/Qwiq.Mapper.Tests/packages.config b/test/Qwiq.Mapper.Tests/packages.config index 452dd73a..db327071 100644 --- a/test/Qwiq.Mapper.Tests/packages.config +++ b/test/Qwiq.Mapper.Tests/packages.config @@ -1,11 +1,11 @@  - + - + \ No newline at end of file diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index f14f2cd2..0ed5ed2e 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -24,14 +24,14 @@ ..\..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll diff --git a/test/Qwiq.Mocks/app.config b/test/Qwiq.Mocks/app.config index 6bafe6a9..8dbaaccf 100644 --- a/test/Qwiq.Mocks/app.config +++ b/test/Qwiq.Mocks/app.config @@ -4,7 +4,7 @@ - + @@ -12,12 +12,20 @@ - + + + + + + + + + diff --git a/test/Qwiq.Mocks/packages.config b/test/Qwiq.Mocks/packages.config index c49c8303..31e72ac2 100644 --- a/test/Qwiq.Mocks/packages.config +++ b/test/Qwiq.Mocks/packages.config @@ -1,8 +1,8 @@  - + - + \ No newline at end of file From 04a86e1072e9fb9bf370ed4c1404cc4b2486b6c4 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 1 May 2017 15:37:27 -0700 Subject: [PATCH 189/251] Code clean up --- src/Qwiq.Core.Rest/LinkCollection.cs | 1 - src/Qwiq.Core.Rest/WorkItem.cs | 1 - .../Exceptions/AggregateExceptionExploder.cs | 1 - src/Qwiq.Core/IWorkItemStore.Extensions.cs | 4 +- src/Qwiq.Core/IWorkItemType.Extensions.cs | 8 ++- src/Qwiq.Core/TfsConnectionFactory.cs | 12 ++--- src/Qwiq.Core/WorkItemCommon.cs | 6 +-- src/Qwiq.Mapper/FieldMapper.cs | 52 ++++++++----------- .../LargeWiqlHierarchyQueryTests.cs | 3 -- .../Mocks/MockModel.cs | 2 - test/Qwiq.Mocks/MockTeamFoundationIdentity.cs | 1 - 11 files changed, 34 insertions(+), 57 deletions(-) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index 1511a74d..e8d54ac2 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -14,7 +14,6 @@ internal class LinkCollection : ReadOnlyObjectWithNameCollection, ICollec { public LinkCollection([CanBeNull] List relations, [NotNull] Func linkFunc) { - Contract.Requires(linkFunc != null); if (relations == null) return; diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index a9cf1339..5e0dc84b 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -6,7 +6,6 @@ using JetBrains.Annotations; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; -using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Client.Rest { diff --git a/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs b/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs index 8a656bfd..2792be90 100644 --- a/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs +++ b/src/Qwiq.Core/Exceptions/AggregateExceptionExploder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; namespace Microsoft.Qwiq.Exceptions { diff --git a/src/Qwiq.Core/IWorkItemStore.Extensions.cs b/src/Qwiq.Core/IWorkItemStore.Extensions.cs index 3273b0be..8d020052 100644 --- a/src/Qwiq.Core/IWorkItemStore.Extensions.cs +++ b/src/Qwiq.Core/IWorkItemStore.Extensions.cs @@ -20,7 +20,5 @@ public static IWorkItemLinkTypeEnd GetParentLinkTypeEnd(this IWorkItemStore stor { return store.GetChildLinkTypeEnd().OppositeEnd; } - - } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemType.Extensions.cs b/src/Qwiq.Core/IWorkItemType.Extensions.cs index b1e2ae17..e95968f1 100644 --- a/src/Qwiq.Core/IWorkItemType.Extensions.cs +++ b/src/Qwiq.Core/IWorkItemType.Extensions.cs @@ -1,10 +1,9 @@ -using System; +using JetBrains.Annotations; +using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; -using JetBrains.Annotations; - namespace Microsoft.Qwiq { public static partial class Extensions @@ -38,8 +37,7 @@ public static IEnumerable NewWorkItems( if (wit == null) throw new ArgumentNullException(nameof(wit)); if (values == null) throw new ArgumentNullException(nameof(values)); - return values.Select(wit.NewWorkItem); } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/TfsConnectionFactory.cs b/src/Qwiq.Core/TfsConnectionFactory.cs index 42f5bbfc..b5d826ee 100644 --- a/src/Qwiq.Core/TfsConnectionFactory.cs +++ b/src/Qwiq.Core/TfsConnectionFactory.cs @@ -1,13 +1,11 @@ -using System; - -using Microsoft.Qwiq.Credentials; +using Microsoft.Qwiq.Credentials; using Microsoft.VisualStudio.Services.Common; +using System; namespace Microsoft.Qwiq { public abstract class TfsConnectionFactory : ITfsConnectionFactory where TConnection : ITeamProjectCollection - { public virtual ITeamProjectCollection Create(AuthenticationOptions options) { @@ -19,17 +17,17 @@ public virtual ITeamProjectCollection Create(AuthenticationOptions options) try { var tfs = ConnectToTfsCollection(options.Uri, credential); - options.Notifications.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfs)); + options.Notifications?.AuthenticationSuccess(new AuthenticationSuccessNotification(credential, tfs)); return tfs; } catch (Exception e) { - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); + options.Notifications?.AuthenticationFailed(new AuthenticationFailedNotification(credential, e)); } } var nocreds = new AccessDeniedException("Invalid credentials"); - options.Notifications.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); + options.Notifications?.AuthenticationFailed(new AuthenticationFailedNotification(null, nocreds)); throw nocreds; } diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index 05109c16..e4ac8876 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -10,7 +10,7 @@ protected internal WorkItemCommon() } protected internal WorkItemCommon(Dictionary fields) - :base(fields) + : base(fields) { } @@ -86,6 +86,8 @@ public virtual string Tags set => SetValue(CoreFieldRefNames.Tags, value); } + public virtual string TeamProject => GetValue(CoreFieldRefNames.TeamProject); + public virtual string Title { get => GetValue(CoreFieldRefNames.Title); @@ -96,8 +98,6 @@ public virtual string Title public virtual string WorkItemType => GetValue(CoreFieldRefNames.WorkItemType); - public virtual string TeamProject => GetValue(CoreFieldRefNames.TeamProject); - public bool Equals(IWorkItemCommon other) { return NullableIdentifiableComparer.Default.Equals(this, other); diff --git a/src/Qwiq.Mapper/FieldMapper.cs b/src/Qwiq.Mapper/FieldMapper.cs index 287191d3..835d65ee 100644 --- a/src/Qwiq.Mapper/FieldMapper.cs +++ b/src/Qwiq.Mapper/FieldMapper.cs @@ -12,38 +12,37 @@ namespace Microsoft.Qwiq.Mapper { public class FieldMapper : IFieldMapper { - public IEnumerable GetWorkItemType(Type type) + public string GetFieldName(Type type, string propertyName) { - if (type == null) throw new ArgumentNullException(nameof(type)); - var customAttributes = type.GetCustomAttributes(typeof(WorkItemTypeAttribute), true).Cast().ToList(); - return customAttributes.Select(ca => ca.GetTypeName()).OrderBy(name => name); - // Order alphabetically so string comparisons work and we don't needlessly permute our queries + var customAttribute = GetFieldAttribute(type, propertyName); + if (customAttribute == null) + throw new ArgumentException( + string.Format( + "No field definition found for property '{0}'. Querying on non-mapped fields is not allowed." + + " Either map the '{0}' property or remove it from the query.", + propertyName), + nameof(propertyName)); + + var fieldName = customAttribute.FieldName; + return fieldName; } public IEnumerable GetFieldNames(Type type) { - var customAttributes = - type.GetProperties() - .SelectMany(property => property.GetCustomAttributes(typeof(FieldDefinitionAttribute), true)) - .Cast(); + var customAttributes = type.GetProperties() + .SelectMany(property => property.GetCustomAttributes(typeof(FieldDefinitionAttribute), true)) + .Cast(); var fieldNames = customAttributes.Select(attrib => attrib.FieldName); return fieldNames.OrderBy(name => name); // Order alphabetically so string comparisons work and we don't needlessly permute our queries } - public string GetFieldName(Type type, string propertyName) + public IEnumerable GetWorkItemType(Type type) { - var customAttribute = GetFieldAttribute(type, propertyName); - if (customAttribute == null) - { - throw new ArgumentException( - string.Format( - "No field definition found for property '{0}'. Querying on non-mapped fields is not allowed." - + " Either map the '{0}' property or remove it from the query.", propertyName), nameof(propertyName)); - } - - var fieldName = customAttribute.FieldName; - return fieldName; + if (type == null) throw new ArgumentNullException(nameof(type)); + var customAttributes = type.GetCustomAttributes(typeof(WorkItemTypeAttribute), true).Cast().ToList(); + return customAttributes.Select(ca => ca.GetTypeName()).OrderBy(name => name); + // Order alphabetically so string comparisons work and we don't needlessly permute our queries } [CanBeNull] @@ -55,15 +54,8 @@ private static T GetFieldAttribute([NotNull] Type type, [NotNull] string prop var property = type.GetProperty(propertyName); var customAttributes = Enumerable.Empty(); - if (property != null) - { - customAttributes = - property.GetCustomAttributes(typeof(T), true) - .Cast() - .ToList(); - } + if (property != null) customAttributes = property.GetCustomAttributes(typeof(T), true).Cast().ToList(); return customAttributes.SingleOrDefault(); } } -} - +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs index d0e8a64a..98f2d95d 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs @@ -1,9 +1,6 @@ using System.Collections.Generic; using System.Linq; -using JetBrains.dotMemoryUnit; -using JetBrains.dotMemoryUnit.Kernel; - using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Mocks/MockModel.cs b/test/Qwiq.Mapper.Benchmark.Tests/Mocks/MockModel.cs index f229a28e..e6cc8360 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Mocks/MockModel.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/Mocks/MockModel.cs @@ -4,8 +4,6 @@ using Microsoft.Qwiq.Mapper.Attributes; -using Newtonsoft.Json; - namespace Microsoft.Qwiq.Mapper.Mocks { [WorkItemType("Task")] diff --git a/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs b/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs index 341ef82c..5758700b 100644 --- a/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs +++ b/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.VisualStudio.Services.Common; From 65515c10fe7b669aa890eb7951385aec413e1df2 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 07:39:55 -0700 Subject: [PATCH 190/251] Wrap SOAP WorkItemStore instance in exception handling proxy --- src/Qwiq.Core.Soap/WorkItemStoreFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs index 1884cad9..670f2993 100644 --- a/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs +++ b/src/Qwiq.Core.Soap/WorkItemStoreFactory.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Qwiq.Credentials; +using Microsoft.Qwiq.Exceptions; namespace Microsoft.Qwiq.Client.Soap { @@ -22,7 +23,7 @@ public override IWorkItemStore Create(AuthenticationOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); var tfsProxy = (IInternalTeamProjectCollection)TfsConnectionFactory.Default.Create(options); - return CreateSoapWorkItemStore(tfsProxy); + return ExceptionHandlingDynamicProxyFactory.Create(CreateSoapWorkItemStore(tfsProxy)); } private static IWorkItemStore CreateSoapWorkItemStore(IInternalTeamProjectCollection tfs) From fff9d01830358ef0ff60307f0ee5cdecc70fe7c1 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 09:02:29 -0700 Subject: [PATCH 191/251] Add coreLinkTypeEnd reference names --- src/Qwiq.Core/CoreLinkTypeEndReferenceNames.cs | 12 ++++++++++++ src/Qwiq.Core/Qwiq.Core.csproj | 1 + 2 files changed, 13 insertions(+) create mode 100644 src/Qwiq.Core/CoreLinkTypeEndReferenceNames.cs diff --git a/src/Qwiq.Core/CoreLinkTypeEndReferenceNames.cs b/src/Qwiq.Core/CoreLinkTypeEndReferenceNames.cs new file mode 100644 index 00000000..814fb7f4 --- /dev/null +++ b/src/Qwiq.Core/CoreLinkTypeEndReferenceNames.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Qwiq +{ + public static class CoreLinkTypeEndReferenceNames + { + public const string Parent = "System.LinkTypes.Hierarchy-Forward"; + public const string Child = "System.LinkTypes.Hierarchy-Reverse"; + + public const string Duplicate = "System.LinkTypes.Duplicate-Forward"; + + public const string DuplicateOf = "System.LinkTypes.Duplicate-Reverse"; + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 2d18acb2..0359adff 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -72,6 +72,7 @@ + From 8096d7de2614649e7fe431e14399fd478c1fce5a Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 10:06:53 -0700 Subject: [PATCH 192/251] Intern friendly core field names --- src/Qwiq.Core/CoreFieldRefNames.cs | 64 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Qwiq.Core/CoreFieldRefNames.cs b/src/Qwiq.Core/CoreFieldRefNames.cs index 4cea2e78..d640e0e7 100644 --- a/src/Qwiq.Core/CoreFieldRefNames.cs +++ b/src/Qwiq.Core/CoreFieldRefNames.cs @@ -117,38 +117,38 @@ public static class CoreFieldRefNames public static IReadOnlyDictionary NameLookup { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { AreaId, "Area ID" }, - { AreaPath, "Area Path" }, - { AssignedTo, "Assigned To" }, - { AttachedFileCount, "Attached File Count"}, - { AuthorizedAs, "Authorized As" }, - { AuthorizedDate, "Authorized Date" }, - { BoardColumn, "Board Column" }, - { BoardColumnDone, "Board Column Done" }, - { BoardLane, "Board Lane" }, - { ChangedBy, "Changed By" }, - { ChangedDate, "Changed Date" }, - { CreatedBy, "Created By" }, - { CreatedDate, "Created Date" }, - { Description, "Description" }, - { ExternalLinkCount, "External Link Count" }, - { History, "History" }, - { HyperlinkCount, "Hyperlink Count" }, - { Id, "ID" }, - { IterationId, "Iteration ID" }, - { IterationPath, "Iteration Path" }, - { LinkType, "Link Type" }, - { NodeName, "Node Name" }, - { Reason, "Reason" }, - { RelatedLinkCount, "Related Link Count" }, - { Rev, "Rev" }, - { RevisedDate, "Revised Date" }, - { State, "State" }, - { Tags, "Tags" }, - { TeamProject, "Team Project" }, - { Title, "Title" }, - { Watermark, "Watermark" }, - { WorkItemType, "Work Item Type" } + { AreaId, string.Intern("Area ID") }, + { AreaPath, string.Intern("Area Path") }, + { AssignedTo, string.Intern("Assigned To") }, + { AttachedFileCount, string.Intern("Attached File Count")}, + { AuthorizedAs, string.Intern("Authorized As") }, + { AuthorizedDate, string.Intern("Authorized Date") }, + { BoardColumn, string.Intern("Board Column") }, + { BoardColumnDone, string.Intern("Board Column Done") }, + { BoardLane, string.Intern("Board Lane") }, + { ChangedBy, string.Intern("Changed By") }, + { ChangedDate, string.Intern("Changed Date") }, + { CreatedBy, string.Intern("Created By") }, + { CreatedDate, string.Intern("Created Date") }, + { Description, string.Intern("Description") }, + { ExternalLinkCount, string.Intern("External Link Count") }, + { History, string.Intern("History") }, + { HyperlinkCount, string.Intern("Hyperlink Count") }, + { Id, string.Intern("ID") }, + { IterationId, string.Intern("Iteration ID") }, + { IterationPath, string.Intern("Iteration Path") }, + { LinkType, string.Intern("Link Type") }, + { NodeName, string.Intern("Node Name") }, + { Reason, string.Intern("Reason") }, + { RelatedLinkCount, string.Intern("Related Link Count") }, + { Rev, string.Intern("Rev") }, + { RevisedDate, string.Intern("Revised Date") }, + { State, string.Intern("State") }, + { Tags, string.Intern("Tags") }, + { TeamProject, string.Intern("Team Project") }, + { Title, string.Intern("Title") }, + { Watermark, string.Intern("Watermark") }, + { WorkItemType, string.Intern("Work Item Type") } }; /// From fb4c146f4a428e7bba580a70b3f998067cb07f28 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 10:07:27 -0700 Subject: [PATCH 193/251] Add JetBrains annotations to collection of core link names --- src/Qwiq.Core/CoreLinkTypeReferenceNames.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs b/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs index 33a867f6..a12c175d 100644 --- a/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs +++ b/src/Qwiq.Core/CoreLinkTypeReferenceNames.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using JetBrains.Annotations; namespace Microsoft.Qwiq { @@ -13,6 +13,7 @@ public static class CoreLinkTypeReferenceNames public const string Related = "System.LinkTypes.Related"; /// Returns the set of all core link types. - public static readonly IEnumerable All = new[] { Related, Hierarchy, Dependency, Duplicate }; + [ItemNotNull] + public static readonly string[] All = { Related, Hierarchy, Dependency, Duplicate }; } } \ No newline at end of file From 378e265588a41d7de17efb8bb0a8d3d09ff68085 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 10:09:31 -0700 Subject: [PATCH 194/251] Intern string names when creating field definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NET automatically performs string interning for all string literals. This is done by means of an intern pool – a special table that stores references to all unique strings. However, only string literals are interned during the compile stage, so strings created at runtime are not checked for already being added to the pool. This is circumvented by adding the strings to the pool directly at runtime via `String.Intern`. The trade off is that the strings remain in the intern pool (and memory) for the lifetime of the `AppDomain`. --- src/Qwiq.Core/FieldDefinition.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs index 660239fb..4a3c94a6 100644 --- a/src/Qwiq.Core/FieldDefinition.cs +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -21,8 +21,8 @@ internal FieldDefinition(int id, [NotNull] string referenceName, [NotNull] strin if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - Name = name; - ReferenceName = referenceName; + Name = string.Intern(name); + ReferenceName = string.Intern(referenceName); if (id == 0) { @@ -47,8 +47,8 @@ internal FieldDefinition([NotNull] string referenceName, [NotNull] string name) if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - Name = name; - ReferenceName = referenceName; + Name = string.Intern(name); + ReferenceName = string.Intern(referenceName); if (!CoreFieldRefNames.CoreFieldIdLookup.TryGetValue(referenceName, out int id)) id = FieldDefinitionComparer.Default.GetHashCode(this); From 89aee96ef7d4d5824d4ffdb37900a3129d0458cb Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 10:10:12 -0700 Subject: [PATCH 195/251] String interning for `LinkCollection` --- src/Qwiq.Core.Rest/LinkCollection.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index e8d54ac2..ba92dccf 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -22,23 +22,26 @@ public LinkCollection([CanBeNull] List relations, [NotNull] Fu for (var i = 0; i < relations.Count; i++) { var relation = relations[i]; - if (CoreLinkTypeReferenceNames.Related.Equals(relation.Rel, StringComparison.OrdinalIgnoreCase)) + var rel = string.Intern(relation.Rel); + + if (CoreLinkTypeReferenceNames.Related.Equals(rel, StringComparison.OrdinalIgnoreCase)) { // Last part of the Url is the ID - var lte = linkFunc(relation.Rel).ForwardEnd; + var lte = linkFunc(rel).ForwardEnd; var l = new RelatedLink(ExtractId(relation.Url), lte, ExtractComment(relation.Attributes)); Add(l); } - else if ("Hyperlink".Equals(relation.Rel, StringComparison.OrdinalIgnoreCase)) + else if ("Hyperlink".Equals(rel, StringComparison.OrdinalIgnoreCase)) { var l = new Hyperlink(relation.Url, ExtractComment(relation.Attributes)); Add(l); } - else if ("ArtifactLink".Equals(relation.Rel, StringComparison.OrdinalIgnoreCase)) + else if ("ArtifactLink".Equals(rel, StringComparison.OrdinalIgnoreCase)) { + const string Name = "name"; var l = new ExternalLink( relation.Url, - ExtractProperty(relation.Attributes, "name"), + ExtractProperty(relation.Attributes, Name), ExtractComment(relation.Attributes)); Add(l); } @@ -49,9 +52,9 @@ public LinkCollection([CanBeNull] List relations, [NotNull] Fu { if (relation.Rel.IndexOf('-') > -1) { - var arrRel = relation.Rel.Split('-'); + var arrRel = rel.Split('-'); var lt = linkFunc(arrRel[0]); - var lte = relation.Rel.Equals(lt.ForwardEnd.ImmutableName, StringComparison.OrdinalIgnoreCase) + var lte = rel.Equals(lt.ForwardEnd.ImmutableName, StringComparison.OrdinalIgnoreCase) ? lt.ForwardEnd : lt.ReverseEnd; @@ -64,7 +67,7 @@ public LinkCollection([CanBeNull] List relations, [NotNull] Fu } else { - throw new NotSupportedException($"{relation.Rel} is not supported."); + throw new NotSupportedException($"{rel} is not supported."); } } } @@ -98,7 +101,8 @@ bool ICollection.Remove(ILink item) private static string ExtractComment(IDictionary relationAttributes) { - return ExtractProperty(relationAttributes, "comment"); + const string Comment = "comment"; + return ExtractProperty(relationAttributes, Comment); } private static int ExtractId(string uri) From 835339523712f4a408d3e67c012d819845ef0882 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 11:54:42 -0700 Subject: [PATCH 196/251] Reduce memory allocations in hot paths Collections must be materialized to avoid multiple allocations within transitive closures. This also prevents the iterator from growing the underlying cached list with read only collections since the size of the allocation is known ahead of time --- .../FieldDefinitionCollection.cs | 8 +- src/Qwiq.Core.Rest/Project.cs | 17 ++- src/Qwiq.Core.Rest/Query.cs | 8 +- src/Qwiq.Core.Rest/WorkItem.cs | 101 +++++++++--------- src/Qwiq.Core.Rest/WorkItemStore.cs | 53 +++++---- src/Qwiq.Core.Rest/WorkItemType.cs | 32 +++++- .../FieldDefinitionCollection.cs | 4 +- src/Qwiq.Core.Soap/ProjectCollection.cs | 13 +-- src/Qwiq.Core.Soap/Query.cs | 25 +++-- src/Qwiq.Core.Soap/WorkItemStore.cs | 5 +- src/Qwiq.Core.Soap/WorkItemTypeCollection.cs | 4 +- src/Qwiq.Core/FieldDefinitionCollection.cs | 2 +- src/Qwiq.Core/IWorkItem.Extensions.cs | 6 +- src/Qwiq.Core/NodeCollection.cs | 11 +- src/Qwiq.Core/ProjectCollection.cs | 15 +-- src/Qwiq.Core/ReadOnlyCollectionWithId.cs | 10 +- .../ReadOnlyObjectWithNameCollection.cs | 25 ++--- src/Qwiq.Core/RegisteredLinkTypeCollection.cs | 3 +- src/Qwiq.Core/WorkItemCollection.cs | 13 +-- src/Qwiq.Core/WorkItemLinkTypeCollection.cs | 2 +- .../WorkItemLinkTypeEndCollection.cs | 5 +- src/Qwiq.Core/WorkItemType.cs | 27 +++-- src/Qwiq.Core/WorkItemTypeCollection.cs | 8 +- test/Qwiq.Integration.Tests/Result.cs | 2 +- .../LargeWiqlHierarchyQueryTests.cs | 2 +- test/Qwiq.Mocks/Extensions.cs | 2 +- .../MockFieldDefinitionCollection.cs | 12 ++- test/Qwiq.Mocks/MockProject.cs | 9 +- test/Qwiq.Mocks/MockProjectCollection.cs | 12 ++- test/Qwiq.Mocks/MockQueryByWiql.cs | 2 +- test/Qwiq.Mocks/MockWorkItemStore.cs | 32 ++++-- 31 files changed, 278 insertions(+), 192 deletions(-) diff --git a/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs b/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs index 19aa6065..cab6efc4 100644 --- a/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Rest/FieldDefinitionCollection.cs @@ -8,8 +8,8 @@ namespace Microsoft.Qwiq.Client.Rest { internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { - public FieldDefinitionCollection(IWorkItemStore store) - : base(store?.Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s)) + internal FieldDefinitionCollection(IWorkItemStore store) + : base(store?.Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s).ToList()) { if (store == null) throw new ArgumentNullException(nameof(store)); } @@ -20,11 +20,11 @@ internal FieldDefinitionCollection(IEnumerable typeFi } internal FieldDefinitionCollection(IEnumerable typeFields) - : this(typeFields.Where(p => p != null).Select(s => new FieldDefinition(s))) + : this(typeFields.Where(p => p != null).Select(s => (IFieldDefinition)new FieldDefinition(s)).ToList()) { } - private FieldDefinitionCollection(IEnumerable fieldDefinitions) + private FieldDefinitionCollection(List fieldDefinitions) : base(fieldDefinitions) { } diff --git a/src/Qwiq.Core.Rest/Project.cs b/src/Qwiq.Core.Rest/Project.cs index 6b8c8c27..e3a649c2 100644 --- a/src/Qwiq.Core.Rest/Project.cs +++ b/src/Qwiq.Core.Rest/Project.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using System.Collections.Generic; using JetBrains.Annotations; @@ -23,7 +23,15 @@ internal Project([NotNull] TeamProjectReference project, [NotNull] WorkItemStore .GetWorkItemTypesAsync(project.Name) .GetAwaiter() .GetResult(); - return new WorkItemTypeCollection(wits.Select(s => new WorkItemType(s))); + + var wits2 = new List(wits.Count); + for (var i = 0; i < wits.Count; i++) + { + var wit = wits[i]; + wits2.Add(new WorkItemType(wit)); + } + + return new WorkItemTypeCollection(wits2); }), new Lazy( () => @@ -39,7 +47,8 @@ internal Project([NotNull] TeamProjectReference project, [NotNull] WorkItemStore .GetResult(); // SOAP Client does not return just the root, so return the root's children to match implementation - return new NodeCollection(new Node(result).ChildNodes); + var n = new Node(result).ChildNodes; + return n; }), new Lazy( () => @@ -54,7 +63,7 @@ internal Project([NotNull] TeamProjectReference project, [NotNull] WorkItemStore .GetAwaiter() .GetResult(); - return new NodeCollection(new Node(result).ChildNodes); + return new Node(result).ChildNodes; })) { } diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 1ef1083e..20643929 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -28,7 +28,7 @@ internal class Query : IQuery @"(?asof\s)\'(?.*)\'", RegexOptions.IgnoreCase | RegexOptions.Singleline); - private static readonly ReadOnlyCollection EmptyWorkItems = new ReadOnlyCollection(new List()); + private static readonly List EmptyWorkItems = new List(0); private readonly DateTime? _asOf; @@ -114,7 +114,7 @@ private ReadOnlyCollection RunkLinkQueryImpl() // To avoid an enumerator allocation we are forcing the cast var result2 = (List)result.WorkItemRelations; - var retval = new List(result2.Count); + var retval = new List(result2.Count + 1); for (var index = 0; index < result2.Count; index++) { @@ -137,7 +137,7 @@ IWorkItemLinkTypeEnd EndValueFactory() } [NotNull] - private ReadOnlyCollection RunQueryImpl() + private List RunQueryImpl() { Contract.Ensures(Contract.Result>() != null); @@ -188,7 +188,7 @@ private ReadOnlyCollection RunQueryImpl() } } - return retval.AsReadOnly(); + return retval; } private WorkItemLinkTypeEndCollection WorkItemLinkTypeEndValueFactory() diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index 5e0dc84b..df0e0e00 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -11,63 +11,56 @@ namespace Microsoft.Qwiq.Client.Rest { internal class WorkItem : Qwiq.WorkItem { - private readonly Func _linkFunc; - + [NotNull] private readonly TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem _item; - + [NotNull] + private readonly Func _linkFunc; [CanBeNull] - private LinkCollection _links2; - - - + private IFieldCollection _fields; [CanBeNull] - private IFieldCollection _fields2; + private LinkCollection _links; public WorkItem( [NotNull] TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, [NotNull] IWorkItemType wit, - Func linkFunc - ) + [NotNull] Func linkFunc) : base(wit) { Contract.Requires(item != null); Contract.Requires(wit != null); + Contract.Requires(linkFunc != null); _item = item ?? throw new ArgumentNullException(nameof(item)); - _linkFunc = linkFunc; - - - - - + _linkFunc = linkFunc ?? throw new ArgumentNullException(nameof(linkFunc)); + Url = _item.Url; + Uri = new Uri(_item.Url, UriKind.Absolute); } - - - public override IFieldCollection Fields => _fields2 ?? (_fields2 = new FieldCollection(this, Type.FieldDefinitions, (r, d) => new Field(r, d))); - - public override int Id => _item.Id.GetValueOrDefault(0); - - public override string Keywords + public override int AttachedFileCount { - get => GetValue(WorkItemFields.Keywords); - set => SetValue(WorkItemFields.Keywords, value); + get + { + var fv = GetValue(CoreFieldRefNames.AttachedFileCount); + if (!fv.HasValue) + { + var cnt = Attachments.Count(); + SetValue(CoreFieldRefNames.AttachedFileCount, cnt); + fv = cnt; + } + return fv.GetValueOrDefault(); + } } - public override ICollection Links => _links2 - ?? (_links2 = new LinkCollection((List)_item.Relations, _linkFunc)); - - /// - public override int RelatedLinkCount + public override int ExternalLinkCount { get { var fv = GetValue(CoreFieldRefNames.ExternalLinkCount); if (!fv.HasValue) { - var cnt = Links.Count(p => p.BaseType == BaseLinkType.RelatedLink); + var cnt = Links.Count(p => p.BaseType == BaseLinkType.ExternalLink); SetValue(CoreFieldRefNames.ExternalLinkCount, cnt); fv = cnt; } @@ -75,6 +68,12 @@ public override int RelatedLinkCount } } + public override IFieldCollection Fields => _fields + ?? (_fields = new FieldCollection( + this, + Type.FieldDefinitions, + (r, d) => new Field(r, d))); + public override int HyperlinkCount { get @@ -90,30 +89,27 @@ public override int HyperlinkCount } } - public override int ExternalLinkCount + public override int Id => _item.Id.GetValueOrDefault(0); + + public override string Keywords { - get - { - var fv = GetValue(CoreFieldRefNames.ExternalLinkCount); - if (!fv.HasValue) - { - var cnt = Links.Count(p => p.BaseType == BaseLinkType.ExternalLink); - SetValue(CoreFieldRefNames.ExternalLinkCount, cnt); - fv = cnt; - } - return fv.GetValueOrDefault(); - } + get => GetValue(WorkItemFields.Keywords); + set => SetValue(WorkItemFields.Keywords, value); } - public override int AttachedFileCount + public override ICollection Links => _links + ?? (_links = new LinkCollection((List)_item.Relations, _linkFunc)); + + /// + public override int RelatedLinkCount { get { - var fv = GetValue(CoreFieldRefNames.AttachedFileCount); + var fv = GetValue(CoreFieldRefNames.ExternalLinkCount); if (!fv.HasValue) { - var cnt = Attachments.Count(); - SetValue(CoreFieldRefNames.AttachedFileCount, cnt); + var cnt = Links.Count(p => p.BaseType == BaseLinkType.RelatedLink); + SetValue(CoreFieldRefNames.ExternalLinkCount, cnt); fv = cnt; } return fv.GetValueOrDefault(); @@ -128,14 +124,23 @@ public override int AttachedFileCount protected override object GetValue(string name) { - //return _item.Fields[name]; + if (string.IsNullOrEmpty(name)) return null; + _item.Fields.TryGetValue(name, out object value); return value; } protected override void SetValue(string name, object value) { + if (string.IsNullOrEmpty(name)) return; + _item.Fields[name] = value; } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_item != null); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 8463c023..db3492d2 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -16,15 +16,15 @@ internal class WorkItemStore : IWorkItemStore "(?.*)-(?.*)", RegexOptions.Singleline | RegexOptions.Compiled); - private readonly Lazy _linkTypes; + private IWorkItemLinkTypeCollection _linkTypes; - private readonly Lazy _projects; + private IProjectCollection _projects; private readonly Lazy _queryFactory; private readonly Lazy _tfs; - private readonly Lazy _fieldDefinitions; + private IFieldDefinitionCollection _fieldDefinitions; internal WorkItemStore( Func tpcFactory, @@ -54,24 +54,33 @@ internal WorkItemStore( PageSize = pageSize; - WorkItemLinkTypeCollection ValueFactory() - { - return GetLinks(NativeWorkItemStore.Value); - } - _linkTypes = new Lazy(ValueFactory); - _projects = new Lazy( - () => - { - using (var projectHttpClient = _tfs.Value.GetClient()) - { - var projects = projectHttpClient.GetProjects(ProjectState.All).GetAwaiter().GetResult(); - return new ProjectCollection(projects.Select(project => new Project(project, this)).Cast().ToList()); - } - }); - _fieldDefinitions = new Lazy(() => new FieldDefinitionCollection(this)); + + } + + private WorkItemLinkTypeCollection WorkItemLinkTypeCollectionFactory() + { + return GetLinks(NativeWorkItemStore.Value); + } + + private IProjectCollection ProjectCollectionFactory() + { + using (var projectHttpClient = _tfs.Value.GetClient()) + { + var projects = (List)projectHttpClient.GetProjects(ProjectState.All).GetAwaiter().GetResult(); + var projects2 = new List(projects.Count + 1); + + for (var i = 0; i < projects.Count; i++) + { + var project = projects[i]; + var p = new Project(project, this); + projects2.Add(p); + } + + return new ProjectCollection(projects2); + } } public int PageSize { get; } @@ -80,9 +89,9 @@ WorkItemLinkTypeCollection ValueFactory() public VssCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; - public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions.Value; + public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions ?? (_fieldDefinitions = new FieldDefinitionCollection(this)); - public IProjectCollection Projects => _projects.Value; + public IProjectCollection Projects => _projects ?? (_projects = ProjectCollectionFactory()); public ITeamProjectCollection TeamProjectCollection => _tfs.Value; @@ -90,7 +99,7 @@ WorkItemLinkTypeCollection ValueFactory() public TimeZone TimeZone => TeamProjectCollection?.TimeZone ?? TimeZone.CurrentTimeZone; - public IWorkItemLinkTypeCollection WorkItemLinkTypes => _linkTypes.Value; + public IWorkItemLinkTypeCollection WorkItemLinkTypes => _linkTypes ?? (_linkTypes = WorkItemLinkTypeCollectionFactory()); public void Dispose() { @@ -202,7 +211,7 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo } } - return new WorkItemLinkTypeCollection(d2.Values); + return new WorkItemLinkTypeCollection(d2.Values.OfType().ToList()); } private void Dispose(bool disposing) diff --git a/src/Qwiq.Core.Rest/WorkItemType.cs b/src/Qwiq.Core.Rest/WorkItemType.cs index 0a7a1a3a..16ad01e9 100644 --- a/src/Qwiq.Core.Rest/WorkItemType.cs +++ b/src/Qwiq.Core.Rest/WorkItemType.cs @@ -1,19 +1,35 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq.Client.Rest { internal class WorkItemType : Qwiq.WorkItemType { - internal WorkItemType(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemType type) + + [NotNull] + private readonly TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemType _type; + + [CanBeNull] + private IFieldDefinitionCollection _fdc; + + internal WorkItemType([NotNull] TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemType type) : base( - type?.Name, - type?.Description, - new Lazy(() => new FieldDefinitionCollection(type?.Fields)), + type.Name, + type.Description, + null, NewWorkItemImpl) { - if (type == null) throw new ArgumentNullException(nameof(type)); + Contract.Requires(type != null); + Contract.Requires(type != null); + + _type = type ?? throw new ArgumentNullException(nameof(type)); } + /// + public override IFieldDefinitionCollection FieldDefinitions => _fdc ?? (_fdc = new FieldDefinitionCollection(_type.Fields)); + private static IWorkItem NewWorkItemImpl() { throw new NotSupportedException(); @@ -40,5 +56,11 @@ private static IWorkItem NewWorkItemImpl() * */ } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_type != null); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs index f2ef0c83..8900dfd4 100644 --- a/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core.Soap/FieldDefinitionCollection.cs @@ -6,6 +6,7 @@ namespace Microsoft.Qwiq.Client.Soap { + // REVIEW: Make this implement the interface for IFieldDefinitionCollection and pass everything to the native type internal class FieldDefinitionCollection : Qwiq.FieldDefinitionCollection { internal FieldDefinitionCollection(Tfs.FieldDefinitionCollection innerCollection) @@ -13,7 +14,8 @@ internal FieldDefinitionCollection(Tfs.FieldDefinitionCollection innerCollection innerCollection.Cast() .Select( field => ExceptionHandlingDynamicProxyFactory.Create( - new FieldDefinition(field)))) + new FieldDefinition(field))) + .ToList()) { } } diff --git a/src/Qwiq.Core.Soap/ProjectCollection.cs b/src/Qwiq.Core.Soap/ProjectCollection.cs index 6eacfe3f..5b6f5e0a 100644 --- a/src/Qwiq.Core.Soap/ProjectCollection.cs +++ b/src/Qwiq.Core.Soap/ProjectCollection.cs @@ -6,15 +6,12 @@ namespace Microsoft.Qwiq.Client.Soap { internal class ProjectCollection : Qwiq.ProjectCollection { - - - public ProjectCollection(TeamFoundation.WorkItemTracking.Client.ProjectCollection valueProjects) - :base(valueProjects.Cast() - .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Project(item)))) + internal ProjectCollection(TeamFoundation.WorkItemTracking.Client.ProjectCollection valueProjects) + : base( + valueProjects.Cast() + .Select(item => ExceptionHandlingDynamicProxyFactory.Create(new Project(item))).ToList() + ) { } - - - } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index bec75c77..db7d5b4a 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -28,25 +28,36 @@ internal Query([NotNull] TeamFoundation.WorkItemTracking.Client.Query query, int if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) throw new PageSizeRangeException(); } + private IWorkItemLinkTypeEndCollection _linkTypes; + public IWorkItemLinkTypeEndCollection GetLinkTypes() { - return new WorkItemLinkTypeEndCollection( - _query - .GetLinkTypes() - .Select(item => new WorkItemLinkTypeEnd(item)) - .ToList()); + if (_linkTypes != null) return _linkTypes; + + var lt = _query.GetLinkTypes(); + var lte = new List(lt.Length); + foreach (var l in lt) + { + var item = new WorkItemLinkTypeEnd(l); + lte.Add(item); + } + + var retval = new WorkItemLinkTypeEndCollection(lte); + _linkTypes = retval; + + return _linkTypes; } public IEnumerable RunLinkQuery() { // REVIEW: Create an IWorkItemLinkInfo like IWorkItemLinkTypeEndCollection and IWorkItemCollection - var ends = new Lazy(() => new WorkItemLinkTypeEndCollection(GetLinkTypes())); + return _query.RunLinkQuery() .Select( item => { - IWorkItemLinkTypeEnd LinkTypeEndFactory() => ends.Value.TryGetById(item.LinkTypeId, out IWorkItemLinkTypeEnd end) ? end : null; + IWorkItemLinkTypeEnd LinkTypeEndFactory() => GetLinkTypes().TryGetById(item.LinkTypeId, out IWorkItemLinkTypeEnd end) ? end : null; return new WorkItemLinkInfo(item.SourceId, item.TargetId, new Lazy(LinkTypeEndFactory)); }) diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index 2f76a46e..084c3690 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -44,7 +44,7 @@ internal WorkItemStore( IWorkItemLinkTypeCollection WorkItemLinkTypeCollectionFactory() { - return new WorkItemLinkTypeCollection(_workItemStore.Value.WorkItemLinkTypes.Select(item => new WorkItemLinkType(item))); + return new WorkItemLinkTypeCollection(_workItemStore.Value.WorkItemLinkTypes.Select(item => (IWorkItemLinkType)new WorkItemLinkType(item)).ToList()); } _workItemLinkTypes = new Lazy(WorkItemLinkTypeCollectionFactory); @@ -54,7 +54,8 @@ IRegisteredLinkTypeCollection RegisteredLinkTypeCollectionFactory() return new RegisteredLinkTypeCollection( _workItemStore .Value.RegisteredLinkTypes.OfType() - .Select(item => new RegisteredLinkType(item.Name))); + .Select(item => (IRegisteredLinkType)new RegisteredLinkType(item.Name)) + .ToList()); } _linkTypes = new Lazy(RegisteredLinkTypeCollectionFactory); diff --git a/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs b/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs index 9ccdcb43..be6d440a 100644 --- a/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs +++ b/src/Qwiq.Core.Soap/WorkItemTypeCollection.cs @@ -10,8 +10,8 @@ public class WorkItemTypeCollection : Qwiq.WorkItemTypeCollection { private readonly TeamFoundation.WorkItemTracking.Client.WorkItemTypeCollection _workItemTypeCollection; - internal WorkItemTypeCollection( - TeamFoundation.WorkItemTracking.Client.WorkItemTypeCollection workItemTypeCollection) + internal WorkItemTypeCollection(TeamFoundation.WorkItemTracking.Client.WorkItemTypeCollection workItemTypeCollection) + :base((List)null) { _workItemTypeCollection = workItemTypeCollection ?? throw new ArgumentNullException(nameof(workItemTypeCollection)); diff --git a/src/Qwiq.Core/FieldDefinitionCollection.cs b/src/Qwiq.Core/FieldDefinitionCollection.cs index afdf0b64..59ccf439 100644 --- a/src/Qwiq.Core/FieldDefinitionCollection.cs +++ b/src/Qwiq.Core/FieldDefinitionCollection.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq { public abstract class FieldDefinitionCollection : ReadOnlyObjectWithIdCollection, IFieldDefinitionCollection { - protected internal FieldDefinitionCollection(IEnumerable fieldDefinitions) + protected internal FieldDefinitionCollection(List fieldDefinitions) : base(fieldDefinitions, definition => definition.Name) { } diff --git a/src/Qwiq.Core/IWorkItem.Extensions.cs b/src/Qwiq.Core/IWorkItem.Extensions.cs index 6c48aef7..7a3c6ec9 100644 --- a/src/Qwiq.Core/IWorkItem.Extensions.cs +++ b/src/Qwiq.Core/IWorkItem.Extensions.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public static partial class Extensions @@ -17,12 +19,12 @@ public static void AddRelatedLink(this IWorkItem workItem, IWorkItemStore store, foreach (var id in targets) workItem.Links.Add(workItem.CreateRelatedLink(id, end)); } - public static IWorkItemCollection ToWorkItemCollection(this IEnumerable items) + public static IWorkItemCollection ToWorkItemCollection([NotNull] [ItemNotNull] [InstantHandle] this IEnumerable items) { if (items == null) throw new ArgumentNullException(nameof(items)); if (items is IWorkItemCollection items2) return items2; - return new WorkItemCollection(items.Distinct(Comparer.WorkItem)); + return new WorkItemCollection(items.Distinct(Comparer.WorkItem).ToList()); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/NodeCollection.cs b/src/Qwiq.Core/NodeCollection.cs index 6d363ff1..30f04303 100644 --- a/src/Qwiq.Core/NodeCollection.cs +++ b/src/Qwiq.Core/NodeCollection.cs @@ -1,15 +1,20 @@ using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { public class NodeCollection : ReadOnlyObjectWithIdCollection, INodeCollection { - internal NodeCollection(params INode[] nodes) - : this(nodes as IEnumerable) + internal NodeCollection([InstantHandle] [NotNull] IEnumerable nodes) + :this(nodes.ToList()) { + Contract.Requires(nodes != null); } - internal NodeCollection(IEnumerable nodes) + internal NodeCollection(List nodes) : base(nodes, node => node.Name) { } diff --git a/src/Qwiq.Core/ProjectCollection.cs b/src/Qwiq.Core/ProjectCollection.cs index 6e9cc9b8..9228de24 100644 --- a/src/Qwiq.Core/ProjectCollection.cs +++ b/src/Qwiq.Core/ProjectCollection.cs @@ -1,28 +1,15 @@ using System; using System.Collections.Generic; -using System.Diagnostics; namespace Microsoft.Qwiq { internal class ProjectCollection : ReadOnlyObjectWithIdCollection, IProjectCollection { - internal ProjectCollection(params IProject[] projects) - : this(projects as IEnumerable) - { - if (projects == null) throw new ArgumentNullException(nameof(projects)); - } - - internal ProjectCollection(IEnumerable projects) + internal ProjectCollection(List projects) : base(projects, project => project.Name) { } public IProject this[Guid id] => GetById(id); - - [DebuggerStepThrough] - public static implicit operator ProjectCollection(Project project) - { - return new ProjectCollection(project); - } } } \ No newline at end of file diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs index 3377b27e..e04f011e 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs @@ -13,13 +13,13 @@ public abstract class ReadOnlyObjectWithIdCollection : ReadOnlyObjectWit private readonly IDictionary _mapById; - protected ReadOnlyObjectWithIdCollection([CanBeNull] IEnumerable items, [CanBeNull] Func nameFunc) + protected ReadOnlyObjectWithIdCollection([CanBeNull] List items, [CanBeNull] Func nameFunc) : this(items, nameFunc, arg => arg.Id) { } protected ReadOnlyObjectWithIdCollection( - [CanBeNull] IEnumerable items, + [CanBeNull] List items, [CanBeNull] Func nameFunc, [NotNull] Func idFunc) : base(items, nameFunc) @@ -27,14 +27,14 @@ protected ReadOnlyObjectWithIdCollection( Contract.Requires(idFunc != null); _idFunc = idFunc ?? throw new ArgumentNullException(nameof(idFunc)); - _mapById = new Dictionary(); + _mapById = new Dictionary(items?.Count ?? 0); } - protected ReadOnlyObjectWithIdCollection([CanBeNull] IEnumerable items) + protected ReadOnlyObjectWithIdCollection([CanBeNull] List items) : base(items) { _idFunc = a => a.Id; - _mapById = new Dictionary(); + _mapById = new Dictionary(items?.Count ?? 0); } public virtual bool Contains(TId id) diff --git a/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs index 747d060c..d6ed0423 100644 --- a/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs +++ b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Linq; using JetBrains.Annotations; @@ -24,7 +23,7 @@ public abstract class ReadOnlyObjectWithNameCollection : IReadOnlyObjectWithN private Func> _itemFactory; - private Lazy> _items; + private Lazy> _lazyItems; private IDictionary _mapByName; @@ -37,13 +36,15 @@ protected ReadOnlyObjectWithNameCollection([NotNull] Func> itemFa _nameFunc = nameFunc ?? throw new ArgumentNullException(nameof(nameFunc)); } - protected ReadOnlyObjectWithNameCollection([CanBeNull] IEnumerable items, [CanBeNull] Func nameFunc) + protected ReadOnlyObjectWithNameCollection([CanBeNull] List items, [CanBeNull] Func nameFunc) { - ItemFactory = () => items ?? Enumerable.Empty(); + List = items ?? new List(0); + _mapByName = new Dictionary(StringComparer.OrdinalIgnoreCase); _nameFunc = nameFunc; + _alreadyInit = false; } - protected ReadOnlyObjectWithNameCollection([CanBeNull] IEnumerable items) + protected ReadOnlyObjectWithNameCollection([CanBeNull] List items) : this(items, null) { } @@ -62,7 +63,7 @@ public virtual int Count } } - protected internal IList List { get; private set; } + protected internal List List { get; private set; } protected Func> ItemFactory { @@ -72,7 +73,7 @@ protected Func> ItemFactory _itemFactory = value; lock (_lockObj) { - _items = new Lazy>(_itemFactory); + _lazyItems = new Lazy>(_itemFactory); Initialize(); } } @@ -151,11 +152,6 @@ T IReadOnlyObjectWithNameCollection.GetItem(int index) return GetItem(index); } - protected internal void Clear() - { - Initialize(); - } - protected virtual void Add(T value, int index) { if (_nameFunc != null) @@ -194,10 +190,11 @@ protected void Ensure() { if (!_alreadyInit) { - if (_items != null) foreach (var item in _items.Value) Add(item); + if (_lazyItems != null) foreach (var item in _lazyItems.Value) Add(item); + else for (var i = 0; i < List.Count; i++) Add(List[i], i); _alreadyInit = true; - _items = null; + _lazyItems = null; } } } diff --git a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs index 9922939e..0849b23e 100644 --- a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs @@ -5,13 +5,14 @@ namespace Microsoft.Qwiq { public class RegisteredLinkTypeCollection : ReadOnlyObjectWithNameCollection, IRegisteredLinkTypeCollection { - public RegisteredLinkTypeCollection(IEnumerable linkTypes) + public RegisteredLinkTypeCollection(List linkTypes) : base(linkTypes, type => type.Name) { } public bool Equals(IRegisteredLinkTypeCollection other) { + // TODO: Implement Equality Comparer throw new NotImplementedException(); } } diff --git a/src/Qwiq.Core/WorkItemCollection.cs b/src/Qwiq.Core/WorkItemCollection.cs index d565504b..a786beba 100644 --- a/src/Qwiq.Core/WorkItemCollection.cs +++ b/src/Qwiq.Core/WorkItemCollection.cs @@ -1,13 +1,12 @@ -using System.Collections.Generic; - -using JetBrains.Annotations; +using JetBrains.Annotations; +using System.Collections.Generic; namespace Microsoft.Qwiq { public class WorkItemCollection : ReadOnlyObjectWithIdCollection, IWorkItemCollection { - public WorkItemCollection(IEnumerable workItems) - :base(workItems) + public WorkItemCollection(List workItems) + : base(workItems) { } @@ -28,7 +27,5 @@ public override int GetHashCode() { return Comparer.WorkItemCollection.GetHashCode(this); } - - } -} +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs index ca0a59ea..d645e1e4 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeCollection.cs @@ -9,7 +9,7 @@ public class WorkItemLinkTypeCollection : ReadOnlyObjectWithNameCollection _ltCol; - public WorkItemLinkTypeCollection(IEnumerable linkTypes) + internal WorkItemLinkTypeCollection(List linkTypes) : base(linkTypes, type => type.ReferenceName) { _ltCol = new Lazy(() => new WorkItemLinkTypeEndCollection(this)); diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs index 03eec353..b5e92de4 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs @@ -9,15 +9,16 @@ public class WorkItemLinkTypeEndCollection : ReadOnlyObjectWithIdCollection linkTypes) : this( linkTypes.SelectMany(s => new[] { s.ForwardEnd, s.IsDirectional ? s.ReverseEnd : null }) - .Where(p => p != null)) + .Where(p => p != null).ToList()) { } - internal WorkItemLinkTypeEndCollection(IEnumerable linkEndTypes) + internal WorkItemLinkTypeEndCollection(List linkEndTypes) : base(linkEndTypes, e => e.Name, e => e.Id) { } + protected override void Add(IWorkItemLinkTypeEnd value, int index) { base.Add(value, index); diff --git a/src/Qwiq.Core/WorkItemType.cs b/src/Qwiq.Core/WorkItemType.cs index 763347ad..67773328 100644 --- a/src/Qwiq.Core/WorkItemType.cs +++ b/src/Qwiq.Core/WorkItemType.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.Contracts; using JetBrains.Annotations; @@ -9,10 +10,15 @@ public class WorkItemType : IWorkItemType, IEquatable { private IFieldDefinitionCollection _fdc; + [CanBeNull] + private readonly Lazy _lazyFieldDefinitions; + + private Func _fieldDefinitionFactory; + internal WorkItemType( [NotNull] string name, [CanBeNull] string description, - [NotNull] Lazy fieldDefinitions, + [CanBeNull] Lazy fieldDefinitions, Func workItemFactory = null) { Contract.Requires(name != null); @@ -21,13 +27,22 @@ internal WorkItemType( if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - FieldDefinitionFactory = () => fieldDefinitions.Value; + + _lazyFieldDefinitions = fieldDefinitions; WorkItemFactory = workItemFactory; - Name = name; - Description = description; + Name = string.Intern(name); + Description = description == null ? string.Empty : string.Intern(description); } - protected internal Func FieldDefinitionFactory { get; internal set; } + protected internal Func FieldDefinitionFactory + { + get => _fieldDefinitionFactory; + internal set + { + Debug.Assert(_lazyFieldDefinitions == null, "_lazyFieldDefinitions == null"); + _fieldDefinitionFactory = value; + } + } protected internal Func WorkItemFactory { get; internal set; } @@ -38,7 +53,7 @@ public bool Equals([CanBeNull] IWorkItemType other) public string Description { get; } - public IFieldDefinitionCollection FieldDefinitions => _fdc ?? (_fdc = FieldDefinitionFactory()); + public virtual IFieldDefinitionCollection FieldDefinitions => _fdc ?? (_fdc = FieldDefinitionFactory == null ? _lazyFieldDefinitions.Value : FieldDefinitionFactory()); public string Name { get; } diff --git a/src/Qwiq.Core/WorkItemTypeCollection.cs b/src/Qwiq.Core/WorkItemTypeCollection.cs index 08285ecb..0ef9ff54 100644 --- a/src/Qwiq.Core/WorkItemTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemTypeCollection.cs @@ -13,13 +13,7 @@ internal WorkItemTypeCollection(Func> workItemTypesFa } [DebuggerStepThrough] - internal WorkItemTypeCollection(params IWorkItemType[] workItemTypes) - : this(workItemTypes as IEnumerable) - { - } - - [DebuggerStepThrough] - internal WorkItemTypeCollection(IEnumerable workItemTypes) + internal WorkItemTypeCollection(List workItemTypes) : base(workItemTypes, type => type.Name) { } diff --git a/test/Qwiq.Integration.Tests/Result.cs b/test/Qwiq.Integration.Tests/Result.cs index cef5b4c4..7f53afe0 100644 --- a/test/Qwiq.Integration.Tests/Result.cs +++ b/test/Qwiq.Integration.Tests/Result.cs @@ -15,7 +15,7 @@ public IWorkItem WorkItem set { _workItem = value; - WorkItems = new WorkItemCollection(new[] { value }); + WorkItems = new WorkItemCollection(new List(new[] { value })); } } diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs index 98f2d95d..1dc88591 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs @@ -80,5 +80,5 @@ public void WorkItems_Equal() } } - + } \ No newline at end of file diff --git a/test/Qwiq.Mocks/Extensions.cs b/test/Qwiq.Mocks/Extensions.cs index 21c7c333..0adc4d32 100644 --- a/test/Qwiq.Mocks/Extensions.cs +++ b/test/Qwiq.Mocks/Extensions.cs @@ -100,7 +100,7 @@ public static MockWorkItemStore Add( public static MockWorkItemStore WithLinkType(this MockWorkItemStore store, params IWorkItemLinkType[] linkTypes) { - store.WorkItemLinkTypes = new WorkItemLinkTypeCollection(store.WorkItemLinkTypes.Union(linkTypes)); + store.WorkItemLinkTypes = new WorkItemLinkTypeCollection(store.WorkItemLinkTypes.Union(linkTypes).ToList()); return store; } diff --git a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs index d541ded8..2960e6ff 100644 --- a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs +++ b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs @@ -1,6 +1,8 @@ +using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; namespace Microsoft.Qwiq.Mocks @@ -8,17 +10,23 @@ namespace Microsoft.Qwiq.Mocks public class MockFieldDefinitionCollection : FieldDefinitionCollection { public MockFieldDefinitionCollection(IWorkItemStore store) - : base(store?.Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s)) + : base(store?.Projects.SelectMany(s => s.WorkItemTypes).SelectMany(s => s.FieldDefinitions).Select(s => s).ToList()) { if (store == null) throw new ArgumentNullException(nameof(store)); } [DebuggerStepThrough] - public MockFieldDefinitionCollection(IEnumerable fieldDefinitions) + public MockFieldDefinitionCollection(List fieldDefinitions) : base(fieldDefinitions) { } + public MockFieldDefinitionCollection([InstantHandle] [NotNull] IEnumerable fieldDefinitions) + : this(fieldDefinitions.ToList()) + { + Contract.Requires(fieldDefinitions != null); + } + public override IFieldDefinition this[string name] { get diff --git a/test/Qwiq.Mocks/MockProject.cs b/test/Qwiq.Mocks/MockProject.cs index e9f48f09..59ad69cc 100644 --- a/test/Qwiq.Mocks/MockProject.cs +++ b/test/Qwiq.Mocks/MockProject.cs @@ -18,7 +18,7 @@ public MockProject(Guid id, string name, Uri uri, IWorkItemTypeCollection wits, { } - internal MockProject(IWorkItemStore store, INode node) + public MockProject(IWorkItemStore store, INode node) : base( Guid.NewGuid(), node.Name, @@ -29,6 +29,11 @@ internal MockProject(IWorkItemStore store, INode node) { } + public MockProject(IWorkItemStore store) + :this(store, new Node(1, false, false, ProjectName, new Uri("http://localhost/projects/1"))) + { + } + private static INodeCollection CreateNodes(bool area) { var root = new Node(1, area, !area, "Root", new Uri("http://localhost/nodes/1")); @@ -51,7 +56,7 @@ private static INodeCollection CreateNodes(bool area) c => Enumerable.Empty()) }); - return new NodeCollection(root); + return new NodeCollection(new[] { root }.ToList().AsReadOnly()); } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockProjectCollection.cs b/test/Qwiq.Mocks/MockProjectCollection.cs index c555902d..d2c9128c 100644 --- a/test/Qwiq.Mocks/MockProjectCollection.cs +++ b/test/Qwiq.Mocks/MockProjectCollection.cs @@ -1,16 +1,22 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Qwiq.Mocks { internal class MockProjectCollection : ProjectCollection { public MockProjectCollection(IWorkItemStore store) - : base(new MockProject(store, new Node(1, false, false, MockProject.ProjectName, new Uri("http://localhost/projects/1")))) + : this(new MockProject(store)) { } - public MockProjectCollection(IEnumerable projects) + public MockProjectCollection(MockProject project) + : this(new[] { (IProject)project }.ToList()) + { + } + + public MockProjectCollection(List projects) : base(projects) { } @@ -19,7 +25,7 @@ public MockProjectCollection(IEnumerable projects) internal class MockWorkItemTypeCollection : WorkItemTypeCollection { public MockWorkItemTypeCollection(IWorkItemStore store) - : base((IWorkItemType[])null) + : base((List)null) { ItemFactory = () => new[] { diff --git a/test/Qwiq.Mocks/MockQueryByWiql.cs b/test/Qwiq.Mocks/MockQueryByWiql.cs index 706f1429..7781d1a7 100644 --- a/test/Qwiq.Mocks/MockQueryByWiql.cs +++ b/test/Qwiq.Mocks/MockQueryByWiql.cs @@ -164,7 +164,7 @@ private static bool MatchAggregate(bool m, Tuple e, IWor public IWorkItemCollection RunQuery() { - return new WorkItemCollection(RunQueryImpl()); + return new WorkItemCollection(RunQueryImpl().ToList()); } private IEnumerable RunQueryImpl() diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index da336a0c..5cb1c8ad 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -48,7 +48,7 @@ Func queryFactory _queryFactory = new Lazy(() => queryFactory(this)); _projects = new Lazy(() => new MockProjectCollection(this)); - WorkItemLinkTypes = new WorkItemLinkTypeCollection(CoreLinkTypeReferenceNames.All.Select(s => new MockWorkItemLinkType(s))); + WorkItemLinkTypes = new WorkItemLinkTypeCollection(CoreLinkTypeReferenceNames.All.Select(s => (IWorkItemLinkType)new MockWorkItemLinkType(s)).ToList()); _lookup = new Dictionary(); LinkInfo = new List(); _storeDefinitions = new Lazy(() => new MockFieldDefinitionCollection(this)); @@ -140,18 +140,28 @@ internal void BatchSave(IEnumerable workItems) { var projectName = item[CoreFieldRefNames.TeamProject].ToString(); var witName = item[CoreFieldRefNames.WorkItemType].ToString(); - var project = Projects[projectName]; + IProject project; + try + { + project = Projects[projectName]; + } + catch (DeniedOrNotExistException) + { + Trace.TraceWarning("Project {0} missing from work item store.", projectName); + project = new MockProject(this); + } if (!project.WorkItemTypes.Contains(witName)) - { - Trace.TraceWarning("Project {0} is missing work item type definition {1}", project, witName); - missingWits.TryAdd(project, new HashSet(WorkItemTypeComparer.Default)); + { + Trace.TraceWarning("Project {0} is missing work item type definition {1}", project, witName); + missingWits.TryAdd(project, new HashSet(WorkItemTypeComparer.Default)); - var t = item.Type as MockWorkItemType; - if (t?.Store != this) t.Store = this; + var t = item.Type as MockWorkItemType; + if (t?.Store != this) t.Store = this; + + missingWits[project].Add(item.Type); + } - missingWits[project].Add(item.Type); - } } // Fourth: If there are any missing wits update the project and reset the project collection @@ -177,7 +187,7 @@ internal void BatchSave(IEnumerable workItems) { changesRequired = true; wits.UnionWith(project.WorkItemTypes); - var w = new WorkItemTypeCollection(wits); + var w = new WorkItemTypeCollection(wits.ToList()); var p = new MockProject( project.Guid, project.Name, @@ -189,6 +199,8 @@ internal void BatchSave(IEnumerable workItems) } } + + if (changesRequired) _projects = new Lazy(() => new MockProjectCollection(newProjects)); } From 0e1f24beaa3690da8cfb97d9740cbfd62bed96b7 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 10:57:47 -0700 Subject: [PATCH 197/251] Add duration test REST implementations should always be faster than SOAP implementations --- ...ItemStoreComparisonContextSpecification.cs | 18 +++++++- .../Qwiq.Tests.Common.csproj | 6 +++ .../TimedContextSpecification.cs | 44 ++++++++++++++----- test/Qwiq.Tests.Common/packages.config | 2 + 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs index 16df963c..de400240 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs @@ -1,6 +1,10 @@ -using Microsoft.Qwiq.Tests.Common; +using System; + +using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Should; + namespace Microsoft.Qwiq.WorkItemStore { [DeploymentItem("Microsoft.WITDataStore32.dll")] @@ -31,5 +35,17 @@ public override void Given() RestResult = new Result { WorkItemStore = TimedAction(() => IntegrationSettings.CreateRestStore(), "REST", "Create WIS") }; } + + [TestMethod] + [TestCategory("REST")] + [TestCategory("SOAP")] + [TestCategory("localOnly")] + public void REST_implementation_is_faster_than_SOAP() + { + Durations.TryGetValue("SOAP", out TimeSpan soapTime); + Durations.TryGetValue("REST", out TimeSpan restTime); + + restTime.ShouldBeLessThan(soapTime); + } } } \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index f40315d9..ba5d943c 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -16,12 +16,18 @@ + + ..\..\packages\Humanizer.Core.2.1.0\lib\netstandard1.0\Humanizer.dll + ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll ..\..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\..\packages\Should.1.1.20\lib\Should.dll + diff --git a/test/Qwiq.Tests.Common/TimedContextSpecification.cs b/test/Qwiq.Tests.Common/TimedContextSpecification.cs index f237dca9..9545a62e 100644 --- a/test/Qwiq.Tests.Common/TimedContextSpecification.cs +++ b/test/Qwiq.Tests.Common/TimedContextSpecification.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Diagnostics; +using Humanizer; + namespace Microsoft.Qwiq.Tests.Common { public abstract class TimedContextSpecification : ContextSpecification { - private readonly IDictionary _durations = new Dictionary(StringComparer.OrdinalIgnoreCase); + public IDictionary Durations { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); protected void TimedAction(Action action, string category, string userMessage) { @@ -19,13 +21,13 @@ protected void TimedAction(Action action, string category, string userMessage) { var stop = Clock.GetTimestamp(); var duration = Clock.GetTimeSpan(start, stop); - Debug.Print("{0}: {1} {2}", category, duration, userMessage); - if (!_durations.ContainsKey(category)) + Debug.Print("{0}: {1} {2}", category, duration.Humanize(), userMessage); + if (!Durations.ContainsKey(category)) { - _durations[category] = TimeSpan.Zero; + Durations[category] = TimeSpan.Zero; } - _durations[category] += duration; + Durations[category] += duration; } } @@ -40,22 +42,42 @@ protected T TimedAction(Func action, string category, string userMessage) { var stop = Clock.GetTimestamp(); var duration = Clock.GetTimeSpan(start, stop); - Debug.Print("{0}: {1} {2}", category, duration, userMessage); - if (!_durations.ContainsKey(category)) + Debug.Print("{0}: {1} {2}", category, duration.Humanize(), userMessage); + if (!Durations.ContainsKey(category)) { - _durations[category] = TimeSpan.Zero; + Durations[category] = TimeSpan.Zero; } - _durations[category] += duration; + Durations[category] += duration; } } public override void Cleanup() { - foreach (var category in _durations) + foreach (var category in Durations) { - Debug.Print("{0}: {1} Total", category.Key, category.Value); + Debug.Print("{0}: {1} Total", category.Key, category.Value.Humanize()); } + + var hasSoap = Durations.TryGetValue("SOAP", out TimeSpan soapTime); + var hasRest = Durations.TryGetValue("REST", out TimeSpan restTime); + + if (!hasSoap || !hasRest) return; + + var p50 = new TimeSpan((long)Math.Round(soapTime.Ticks * 1.5)); + var p75 = new TimeSpan((long)Math.Round(soapTime.Ticks * 1.25)); + var p90 = new TimeSpan((long)Math.Round(soapTime.Ticks * 1.10)); + var p95 = new TimeSpan((long)Math.Round(soapTime.Ticks * 1.05)); + + var restBetterThanp50 = restTime <= p50; + var restBetterThanp75 = restTime <= p75; + var restBetterThanp90 = restTime <= p90; + var restBetterThanp95 = restTime <= p95; + + Debug.WriteLineIf(!restBetterThanp50, $"REST is not better than 50% of SOAP time. Expected {restTime.Humanize()} < {p50.Humanize()}"); + Debug.WriteLineIf(!restBetterThanp75, $"REST is not better than 75% of SOAP time. Expected {restTime.Humanize()} < {p75.Humanize()}"); + Debug.WriteLineIf(!restBetterThanp90, $"REST is not better than 90% of SOAP time. Expected {restTime.Humanize()} < {p90.Humanize()}"); + Debug.WriteLineIf(!restBetterThanp95, $"REST is not better than 95% of SOAP time. Expected {restTime.Humanize()} < {p95.Humanize()}"); } } } \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/packages.config b/test/Qwiq.Tests.Common/packages.config index d1e45b36..6cba4492 100644 --- a/test/Qwiq.Tests.Common/packages.config +++ b/test/Qwiq.Tests.Common/packages.config @@ -1,7 +1,9 @@  + + \ No newline at end of file From 549f139ba5bba023fa9761306a1adcede52fbffb Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 16:13:30 -0700 Subject: [PATCH 198/251] Intern project name --- src/Qwiq.Core/Project.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Core/Project.cs b/src/Qwiq.Core/Project.cs index bf524fde..8a6d30b7 100644 --- a/src/Qwiq.Core/Project.cs +++ b/src/Qwiq.Core/Project.cs @@ -20,7 +20,7 @@ internal Project( Lazy iteration) { Guid = guid; - Name = name ?? throw new ArgumentNullException(nameof(name)); + Name = name != null ? string.Intern(name) : throw new ArgumentNullException(nameof(name)); Uri = uri ?? throw new ArgumentNullException(nameof(uri)); _wits = wits ?? throw new ArgumentNullException(nameof(wits)); _area = area ?? throw new ArgumentNullException(nameof(area)); From 3bd5ad9a77a2a41a262ad5650ed067170b0a0bcb Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 16:13:51 -0700 Subject: [PATCH 199/251] Update R# coverage filters --- Qwiq.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Qwiq.sln.DotSettings b/Qwiq.sln.DotSettings index b04cc28d..98c320a8 100644 --- a/Qwiq.sln.DotSettings +++ b/Qwiq.sln.DotSettings @@ -1,2 +1,2 @@  - <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Integration.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Visitors.Utility" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Visitors.LocalCollectionExpander" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.BenchmarkTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.BenchmarkTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.IntegrationTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Microsoft.Qwiq.Relatives" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="Qwiq.Identity.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="Qwiq.Benchmark.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Tests.Common.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mocks" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mocks.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Relatives.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Core.Tests.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Relatives.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Benchmark.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Tests.Common" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Integration.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Benchmark" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Visitors.Utility" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.Visitors.LocalCollectionExpander" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Identity.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Linq.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.BenchmarkTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.BenchmarkTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.IntegrationTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core.UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Client.Rest" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Client.Rest.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Linq" ModuleVersionMask="*" ClassMask="JetBrains.Annotations.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper" ModuleVersionMask="*" ClassMask="JetBrains.Annotations.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Core" ModuleVersionMask="*" ClassMask="JetBrains.Annotations.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Client.Soap" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Client.Soap.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Mapper.Identity" ModuleVersionMask="*" ClassMask="Microsoft.Qwiq.Mapper.GitVersionInformation" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Microsoft.Qwiq.Identity" ModuleVersionMask="*" ClassMask="JetBrains.Annotations.*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file From c692c1c042fbac4417220ff92af7e8b91bc50dec Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 16:27:46 -0700 Subject: [PATCH 200/251] Make comparers internal --- src/Qwiq.Core/FieldDefinitionCollectionComparer.cs | 2 +- src/Qwiq.Core/FieldDefinitionComparer.cs | 2 +- src/Qwiq.Core/GenericComparer.cs | 4 ++-- src/Qwiq.Core/IdentifiableComparer.cs | 2 +- src/Qwiq.Core/IdentityDescriptorComparer.cs | 2 +- src/Qwiq.Core/NodeComparer.cs | 2 +- src/Qwiq.Core/NullableIdentifiableComparer.cs | 2 +- src/Qwiq.Core/ProjectComparer.cs | 2 +- src/Qwiq.Core/Properties/AssemblyInfo.cs | 2 ++ src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs | 2 +- src/Qwiq.Core/TeamFoundationIdentityComparer.cs | 2 +- src/Qwiq.Core/WorkItemCollectionComparer.cs | 2 +- src/Qwiq.Core/WorkItemComparer.cs | 2 +- src/Qwiq.Core/WorkItemLinkInfoComparer.cs | 2 +- src/Qwiq.Core/WorkItemLinkTypeComparer.cs | 2 +- src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs | 2 +- src/Qwiq.Core/WorkItemTypeCollectionComparer.cs | 2 +- src/Qwiq.Core/WorkItemTypeComparer.cs | 2 +- 18 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs b/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs index 9372a30a..e8eabd71 100644 --- a/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionCollectionComparer.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public class FieldDefinitionCollectionComparer : GenericComparer + internal class FieldDefinitionCollectionComparer : GenericComparer { internal static readonly string[] SkippedFields = { diff --git a/src/Qwiq.Core/FieldDefinitionComparer.cs b/src/Qwiq.Core/FieldDefinitionComparer.cs index 2b2b996f..b1d54750 100644 --- a/src/Qwiq.Core/FieldDefinitionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionComparer.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - public class FieldDefinitionComparer : GenericComparer + internal class FieldDefinitionComparer : GenericComparer { private FieldDefinitionComparer() { diff --git a/src/Qwiq.Core/GenericComparer.cs b/src/Qwiq.Core/GenericComparer.cs index 3c8f85aa..9351b860 100644 --- a/src/Qwiq.Core/GenericComparer.cs +++ b/src/Qwiq.Core/GenericComparer.cs @@ -6,11 +6,11 @@ namespace Microsoft.Qwiq { - public class GenericComparer : IComparer, IEqualityComparer + internal class GenericComparer : IComparer, IEqualityComparer { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - public static readonly GenericComparer Default = new GenericComparer(); + internal static readonly GenericComparer Default = new GenericComparer(); public virtual int Compare(T x, T y) { diff --git a/src/Qwiq.Core/IdentifiableComparer.cs b/src/Qwiq.Core/IdentifiableComparer.cs index 0d9036b5..11f3ea1e 100644 --- a/src/Qwiq.Core/IdentifiableComparer.cs +++ b/src/Qwiq.Core/IdentifiableComparer.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public class IdentifiableComparer : GenericComparer> + internal class IdentifiableComparer : GenericComparer> { internal new static IdentifiableComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/IdentityDescriptorComparer.cs b/src/Qwiq.Core/IdentityDescriptorComparer.cs index 2b97ca8a..ba7078a0 100644 --- a/src/Qwiq.Core/IdentityDescriptorComparer.cs +++ b/src/Qwiq.Core/IdentityDescriptorComparer.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public class IdentityDescriptorComparer : GenericComparer + internal class IdentityDescriptorComparer : GenericComparer { internal new static IdentityDescriptorComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/NodeComparer.cs b/src/Qwiq.Core/NodeComparer.cs index 96a55e7c..e7e7488f 100644 --- a/src/Qwiq.Core/NodeComparer.cs +++ b/src/Qwiq.Core/NodeComparer.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - public class NodeComparer : GenericComparer + internal class NodeComparer : GenericComparer { internal new static readonly NodeComparer Default = Nested.Instance; diff --git a/src/Qwiq.Core/NullableIdentifiableComparer.cs b/src/Qwiq.Core/NullableIdentifiableComparer.cs index a501c928..8b5e31cd 100644 --- a/src/Qwiq.Core/NullableIdentifiableComparer.cs +++ b/src/Qwiq.Core/NullableIdentifiableComparer.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public class NullableIdentifiableComparer : GenericComparer> + internal class NullableIdentifiableComparer : GenericComparer> { internal new static NullableIdentifiableComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/ProjectComparer.cs b/src/Qwiq.Core/ProjectComparer.cs index 38a1d19b..817604e8 100644 --- a/src/Qwiq.Core/ProjectComparer.cs +++ b/src/Qwiq.Core/ProjectComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class ProjectComparer : GenericComparer + internal class ProjectComparer : GenericComparer { internal new static ProjectComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/Properties/AssemblyInfo.cs b/src/Qwiq.Core/Properties/AssemblyInfo.cs index 3c52df59..ffd308bd 100644 --- a/src/Qwiq.Core/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core/Properties/AssemblyInfo.cs @@ -20,3 +20,5 @@ [assembly: InternalsVisibleTo("Microsoft.Qwiq.IntegrationTests")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Core.UnitTests")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Soap")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Mapper")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Mapper.Identity")] diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs b/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs index 7891b638..0d6dd215 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithIdComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class ReadOnlyCollectionWithIdComparer : GenericComparer> + internal class ReadOnlyCollectionWithIdComparer : GenericComparer> where T : IIdentifiable { public new static readonly ReadOnlyCollectionWithIdComparer Default = new ReadOnlyCollectionWithIdComparer(); diff --git a/src/Qwiq.Core/TeamFoundationIdentityComparer.cs b/src/Qwiq.Core/TeamFoundationIdentityComparer.cs index e7ebb67a..be8d9bdd 100644 --- a/src/Qwiq.Core/TeamFoundationIdentityComparer.cs +++ b/src/Qwiq.Core/TeamFoundationIdentityComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class TeamFoundationIdentityComparer : GenericComparer + internal class TeamFoundationIdentityComparer : GenericComparer { internal new static TeamFoundationIdentityComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/WorkItemCollectionComparer.cs b/src/Qwiq.Core/WorkItemCollectionComparer.cs index 5673fbe4..52371c3f 100644 --- a/src/Qwiq.Core/WorkItemCollectionComparer.cs +++ b/src/Qwiq.Core/WorkItemCollectionComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class WorkItemCollectionComparer : GenericComparer + internal class WorkItemCollectionComparer : GenericComparer { internal new static WorkItemCollectionComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/WorkItemComparer.cs b/src/Qwiq.Core/WorkItemComparer.cs index 03de8ce8..f1e7c7e2 100644 --- a/src/Qwiq.Core/WorkItemComparer.cs +++ b/src/Qwiq.Core/WorkItemComparer.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public class WorkItemComparer : GenericComparer + internal class WorkItemComparer : GenericComparer { private WorkItemComparer() { diff --git a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs index ce9773ae..3ddc9848 100644 --- a/src/Qwiq.Core/WorkItemLinkInfoComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkInfoComparer.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkInfoComparer : GenericComparer + internal class WorkItemLinkInfoComparer : GenericComparer { internal new static WorkItemLinkInfoComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/WorkItemLinkTypeComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs index f89e9cc4..a4b40834 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeComparer : GenericComparer + internal class WorkItemLinkTypeComparer : GenericComparer { internal new static WorkItemLinkTypeComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs index f8f9edfd..60913cae 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeEndComparer : GenericComparer + internal class WorkItemLinkTypeEndComparer : GenericComparer { internal new static WorkItemLinkTypeEndComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs b/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs index f78a41ac..00cc1c49 100644 --- a/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeCollectionComparer.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq { - public class WorkItemTypeCollectionComparer : GenericComparer + internal class WorkItemTypeCollectionComparer : GenericComparer { internal new static WorkItemTypeCollectionComparer Default => Nested.Instance; diff --git a/src/Qwiq.Core/WorkItemTypeComparer.cs b/src/Qwiq.Core/WorkItemTypeComparer.cs index 4e8c238e..4286bb38 100644 --- a/src/Qwiq.Core/WorkItemTypeComparer.cs +++ b/src/Qwiq.Core/WorkItemTypeComparer.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq { - public class WorkItemTypeComparer : GenericComparer + internal class WorkItemTypeComparer : GenericComparer { internal new static WorkItemTypeComparer Default => Nested.Instance; From 6fecc158b9aacd10aa8e43891d3b447a2e0f73fa Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 19:25:20 -0700 Subject: [PATCH 201/251] Cache value of all core ref fields --- src/Qwiq.Core/CoreFieldRefNames.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Core/CoreFieldRefNames.cs b/src/Qwiq.Core/CoreFieldRefNames.cs index d640e0e7..a9750c95 100644 --- a/src/Qwiq.Core/CoreFieldRefNames.cs +++ b/src/Qwiq.Core/CoreFieldRefNames.cs @@ -72,7 +72,7 @@ public static class CoreFieldRefNames public const string WorkItemType = "System.WorkItemType"; - public static IEnumerable All => NameLookup.Keys.Except(new[] { LinkType }); + public static IEnumerable All => NameLookup.Keys.Except(new[] { LinkType }).ToList(); public static IReadOnlyDictionary CoreFieldIdLookup { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase) From 013eea0f1396525ec315c67082c710f66463974f Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 19:26:03 -0700 Subject: [PATCH 202/251] Add string interning and JetBrains annotations --- src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs | 9 +++++++-- src/Qwiq.Core/RegisteredLinkType.cs | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs index ebdb8f8f..a61a4242 100644 --- a/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; @@ -6,11 +9,13 @@ namespace Microsoft.Qwiq.Client.Rest { internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd { - internal WorkItemLinkTypeEnd(WorkItemRelationType item) + internal WorkItemLinkTypeEnd([NotNull] WorkItemRelationType item) : base(item.ReferenceName) { + Contract.Requires(item != null); + if (item == null) throw new ArgumentNullException(nameof(item)); - Name = item.Name; + Name = string.Intern(item.Name); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/RegisteredLinkType.cs b/src/Qwiq.Core/RegisteredLinkType.cs index 2ecd21b4..14eb6017 100644 --- a/src/Qwiq.Core/RegisteredLinkType.cs +++ b/src/Qwiq.Core/RegisteredLinkType.cs @@ -1,10 +1,16 @@ +using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class RegisteredLinkType : IRegisteredLinkType { - public RegisteredLinkType(string name) + public RegisteredLinkType([NotNull] string name) { - Name = name; + Contract.Requires(!string.IsNullOrEmpty(name)); + Name = name != null ? string.Intern(name) : throw new ArgumentNullException(nameof(name)); } public string Name { get; } From 96d093a22e0cc434d5db03ec4eadb05dc986dfa3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 19:28:03 -0700 Subject: [PATCH 203/251] Convert WorkItemLinkTypeEndCollection to use name Id only exists in the SOAP implementations, whereas name exists in both SOAP and REST --- src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs | 3 +-- src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs index bdb3e60c..7b1db22d 100644 --- a/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/IWorkItemLinkTypeEndCollection.cs @@ -1,7 +1,6 @@ namespace Microsoft.Qwiq { - public interface IWorkItemLinkTypeEndCollection : IReadOnlyObjectWithIdCollection + public interface IWorkItemLinkTypeEndCollection : IReadOnlyObjectWithNameCollection { - } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs index b5e92de4..d51c1a0c 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndCollection.cs @@ -3,7 +3,7 @@ namespace Microsoft.Qwiq { - public class WorkItemLinkTypeEndCollection : ReadOnlyObjectWithIdCollection, + public class WorkItemLinkTypeEndCollection : ReadOnlyObjectWithNameCollection, IWorkItemLinkTypeEndCollection { internal WorkItemLinkTypeEndCollection(IEnumerable linkTypes) @@ -14,11 +14,10 @@ internal WorkItemLinkTypeEndCollection(IEnumerable linkTypes) } internal WorkItemLinkTypeEndCollection(List linkEndTypes) - : base(linkEndTypes, e => e.Name, e => e.Id) + : base(linkEndTypes, e => e.Name) { } - protected override void Add(IWorkItemLinkTypeEnd value, int index) { base.Add(value, index); From e20b9f003a4e0ae9221fa551a3c8a9e1eada0d59 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 19:28:41 -0700 Subject: [PATCH 204/251] Make .ctor internal --- src/Qwiq.Core.Rest/LinkCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Core.Rest/LinkCollection.cs b/src/Qwiq.Core.Rest/LinkCollection.cs index ba92dccf..0276e737 100644 --- a/src/Qwiq.Core.Rest/LinkCollection.cs +++ b/src/Qwiq.Core.Rest/LinkCollection.cs @@ -12,7 +12,7 @@ namespace Microsoft.Qwiq.Client.Rest { internal class LinkCollection : ReadOnlyObjectWithNameCollection, ICollection { - public LinkCollection([CanBeNull] List relations, [NotNull] Func linkFunc) + internal LinkCollection([CanBeNull] List relations, [NotNull] Func linkFunc) { Contract.Requires(linkFunc != null); From bc48b6b755bda19cfec502fa018d49afe75407de Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 19:30:10 -0700 Subject: [PATCH 205/251] Separate out extensions methods Should extensions now only contain extensions for Should --- test/Qwiq.Tests.Common/Extensions.cs | 105 +++++++++++++++ .../Qwiq.Tests.Common.csproj | 1 + test/Qwiq.Tests.Common/ShouldExtensions.cs | 126 +----------------- 3 files changed, 113 insertions(+), 119 deletions(-) create mode 100644 test/Qwiq.Tests.Common/Extensions.cs diff --git a/test/Qwiq.Tests.Common/Extensions.cs b/test/Qwiq.Tests.Common/Extensions.cs new file mode 100644 index 00000000..af927921 --- /dev/null +++ b/test/Qwiq.Tests.Common/Extensions.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Qwiq +{ + public static class Extensions + { + public static string EachToUsefulString(this IEnumerable enumerable) + { + var sb = new StringBuilder(); + sb.AppendLine("{"); + sb.Append(string.Join(",\n", enumerable.Select(x => ToUsefulString(x).Tab()).Take(20).ToArray())); + if (enumerable.Count() > 20) + { + if (enumerable.Count() > 21) + { + sb.AppendLine($",\n ...({enumerable.Count() - 20} more elements)"); + } + else + { + sb.AppendLine(",\n" + enumerable.Last().ToUsefulString().Tab()); + } + } + else + { + sb.AppendLine(); + } + + sb.AppendLine("}"); + + return sb.ToString(); + } + + public static string ToUsefulString(this object obj) + { + string str; + if (obj == null) + { + return "[null]"; + } + + if (obj.GetType() == typeof(string)) + { + str = (string)obj; + return "\"" + str.Replace("\n", "\\n").Replace("\r", "\\r") + "\""; + } + + if (obj.GetType().IsValueType) + { + return "[" + obj + "]"; + } + + if (obj is IEnumerable) + { + var enumerable = ((IEnumerable)obj).Cast(); + + return obj.GetType() + ":\n" + enumerable.EachToUsefulString(); + } + + str = obj.ToString(); + + if (string.IsNullOrEmpty(str)) + { + return $"{obj.GetType()}:[]"; + } + + str = str.Trim(); + + if (str.Contains("\n")) + { + return string.Format("{1}:\r\n[\r\n{0}\r\n]", str.Tab(), obj.GetType()); + } + + if (obj.GetType().ToString() == str) + { + return obj.GetType().ToString(); + } + + return $"{obj.GetType()}:[{str}]"; + } + + private static string Tab(this string str) + { + if (string.IsNullOrEmpty(str)) + { + return string.Empty; + } + + var split = str.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); + var sb = new StringBuilder(); + + sb.Append(" " + split[0]); + foreach (var part in split.Skip(1)) + { + sb.AppendLine(); + sb.Append(" " + part); + } + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index ba5d943c..bd2624e4 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -43,6 +43,7 @@ + diff --git a/test/Qwiq.Tests.Common/ShouldExtensions.cs b/test/Qwiq.Tests.Common/ShouldExtensions.cs index 624fe001..648fa52f 100644 --- a/test/Qwiq.Tests.Common/ShouldExtensions.cs +++ b/test/Qwiq.Tests.Common/ShouldExtensions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; using Microsoft.Qwiq; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -30,129 +27,20 @@ public static void ShouldContainOnly(this IEnumerable collection, IEnumera foreach (var item in expected) { - if (!source.Contains(item, comparer)) - { - noContain.Add(item); - } - else - { - // this only removes the first occurrence, so if the number of occurrences doesn't match, we'll still get a - // valid mismatch between the lists - source.Remove(item); - } + if (!source.Contains(item, comparer)) noContain.Add(item); + else source.Remove(item); } if (noContain.Any() || source.Any()) { - string message = - $"Should contain only: {expected.EachToUsefulString()} \r\nentire list: {collection.EachToUsefulString()}"; + var message = $"Should contain only: {expected.EachToUsefulString()} \r\nentire list: {collection.EachToUsefulString()}"; - if (noContain.Any()) - { - message += "\ndoes not contain: " + noContain.EachToUsefulString(); - } + if (noContain.Any()) message += "\ndoes not contain: " + noContain.EachToUsefulString(); - if (source.Any()) - { - message += "\ndoes contain but shouldn't: " + source.EachToUsefulString(); - } + if (source.Any()) message += "\ndoes contain but shouldn't: " + source.EachToUsefulString(); throw new AssertFailedException(message); } } - - public static string EachToUsefulString(this IEnumerable enumerable) - { - var sb = new StringBuilder(); - sb.AppendLine("{"); - sb.Append(string.Join(",\n", enumerable.Select(x => x.ToUsefulString().Tab()).Take(20).ToArray())); - if (enumerable.Count() > 20) - { - if (enumerable.Count() > 21) - { - sb.AppendLine($",\n ...({enumerable.Count() - 20} more elements)"); - } - else - { - sb.AppendLine(",\n" + enumerable.Last().ToUsefulString().Tab()); - } - } - else - { - sb.AppendLine(); - } - - sb.AppendLine("}"); - - return sb.ToString(); - } - - public static string ToUsefulString(this object obj) - { - string str; - if (obj == null) - { - return "[null]"; - } - - if (obj.GetType() == typeof(string)) - { - str = (string)obj; - return "\"" + str.Replace("\n", "\\n").Replace("\r", "\\r") + "\""; - } - - if (obj.GetType().IsValueType) - { - return "[" + obj + "]"; - } - - if (obj is IEnumerable) - { - var enumerable = ((IEnumerable)obj).Cast(); - - return obj.GetType() + ":\n" + enumerable.EachToUsefulString(); - } - - str = obj.ToString(); - - if (string.IsNullOrEmpty(str)) - { - return $"{obj.GetType()}:[]"; - } - - str = str.Trim(); - - if (str.Contains("\n")) - { - return string.Format("{1}:\r\n[\r\n{0}\r\n]", str.Tab(), obj.GetType()); - } - - if (obj.GetType().ToString() == str) - { - return obj.GetType().ToString(); - } - - return $"{obj.GetType()}:[{str}]"; - } - - private static string Tab(this string str) - { - if (string.IsNullOrEmpty(str)) - { - return string.Empty; - } - - var split = str.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); - var sb = new StringBuilder(); - - sb.Append(" " + split[0]); - foreach (var part in split.Skip(1)) - { - sb.AppendLine(); - sb.Append(" " + part); - } - - return sb.ToString(); - } } -} +} \ No newline at end of file From a18d97c0b9044ed071dc6e3868e0e81309668c7f Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 3 May 2017 19:30:58 -0700 Subject: [PATCH 206/251] Add WorkItemStoreConfiguration --- src/Qwiq.Core.Rest/Query.cs | 161 +++++++++++++----- src/Qwiq.Core.Rest/QueryFactory.cs | 13 +- src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj | 1 + src/Qwiq.Core.Rest/TfsConnectionFactory.cs | 6 + src/Qwiq.Core.Rest/WorkItem.cs | 20 ++- src/Qwiq.Core.Rest/WorkItemStore.cs | 125 +++++++------- .../WorkItemStoreConfiguration.cs | 84 +++++++++ src/Qwiq.Core.Soap/Query.cs | 34 ++-- src/Qwiq.Core.Soap/QueryFactory.cs | 34 ++-- src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs | 9 +- src/Qwiq.Core.Soap/WorkItemStore.cs | 20 +-- src/Qwiq.Core/IWorkItemStore.cs | 5 + src/Qwiq.Core/Qwiq.Core.csproj | 1 + src/Qwiq.Core/ReadOnlyCollectionWithId.cs | 7 + .../ReadOnlyObjectWithNameCollection.cs | 15 +- src/Qwiq.Core/RegisteredLinkTypeCollection.cs | 2 +- src/Qwiq.Core/RelatedLink.cs | 4 +- src/Qwiq.Core/WorkItem.cs | 18 +- src/Qwiq.Core/WorkItemCollection.cs | 7 +- src/Qwiq.Core/WorkItemLinkInfo.cs | 18 +- src/Qwiq.Core/WorkItemLinkType.cs | 22 ++- src/Qwiq.Core/WorkItemLinkTypeEnd.cs | 34 +++- src/Qwiq.Core/WorkItemStoreConfiguration.cs | 49 ++++++ ...ulkIdentityAwareAttributeMapperStrategy.cs | 4 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 3 - .../IntegrationSettings.cs | 15 +- .../LargeWiqlHierarchyQueryTests.cs | 14 +- ...rationContextSpecificationSpecification.cs | 45 +++++ .../WorkItemStore/WorkItem/LinkTests.cs | 30 +++- .../WorkItemStore/WorkItem/MultipleIdTests.cs | 12 ++ ...ItemStoreComparisonContextSpecification.cs | 22 +-- .../Mocks/InstrumentedMockWorkItemStore.cs | 3 + test/Qwiq.Mocks/MockWorkItemStore.cs | 5 + 33 files changed, 631 insertions(+), 211 deletions(-) create mode 100644 src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs create mode 100644 src/Qwiq.Core/WorkItemStoreConfiguration.cs diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 20643929..6ba915da 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -4,6 +4,7 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -14,16 +15,6 @@ namespace Microsoft.Qwiq.Client.Rest { internal class Query : IQuery { - internal const int MaximumBatchSize = 200; - - internal const int MaximumFieldSize = 100; - - // This is defined in Microsoft.TeamFoundation.WorkItemTracking.Client.PageSizes - internal const int MinimumBatchSize = 50; - - // TODO: Make this configurable - private const WorkItemExpand Expand = WorkItemExpand.All; - private static readonly Regex AsOfRegex = new Regex( @"(?asof\s)\'(?.*)\'", RegexOptions.IgnoreCase | RegexOptions.Singleline); @@ -77,7 +68,7 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() [ItemNotNull] public IEnumerable RunLinkQuery() { - return RunkLinkQueryImpl(); + return _workItemStore.Configuration.LazyLoadingEnabled ? RunkLinkQueryImplLazy() : RunkLinkQueryImpl(); } public IWorkItemCollection RunQuery() @@ -85,7 +76,7 @@ public IWorkItemCollection RunQuery() if (_ids == null && _query == null) throw new InvalidOperationException(); // Allocate for method iterator and WorkItemCollection object - return new WorkItemCollection(RunQueryImpl()); + return new WorkItemCollection(_workItemStore.Configuration.LazyLoadingEnabled ? RunQueryImplLazy() : RunQueryImpl()); } private static DateTime? ExtractAsOf(string wiql) @@ -98,12 +89,57 @@ public IWorkItemCollection RunQuery() return retval; } + private WorkItem CreateItemEager(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem workItem) + { + return new WorkItem( + workItem, + // REVIEW: Allocate for reference type + _workItemStore.Projects[(string)workItem.Fields[CoreFieldRefNames.TeamProject]] + .WorkItemTypes[(string)workItem.Fields[CoreFieldRefNames.WorkItemType]], + // REVIEW: Delegate allocation from method group + LinkFunc); + } + + private WorkItem CreateItemLazy(TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem workItem) + { + IWorkItemType WorkItemTypeFactory() + { + return _workItemStore.Projects[(string)workItem.Fields[CoreFieldRefNames.TeamProject]] + .WorkItemTypes[(string)workItem.Fields[CoreFieldRefNames.WorkItemType]]; + } + + return new WorkItem(workItem, new Lazy(WorkItemTypeFactory), LinkFunc); + } + private IWorkItemLinkType LinkFunc(string s) { return _workItemStore.WorkItemLinkTypes[s]; } private ReadOnlyCollection RunkLinkQueryImpl() + { + // Eager loading for the link type ID (which is not returned by the REST API) causes ~250ms delay + var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); + + // To avoid an enumerator allocation we are forcing the cast + var result2 = (List)result.WorkItemRelations; + var retval = new List(result2.Count + 1); + // REVIEW: Closure variable "ends" allocates, preventing local cache + var ends = GetLinkTypes(); + + for (var index = 0; index < result2.Count; index++) + { + ends.TryGetByName(result2[index].Rel, out IWorkItemLinkTypeEnd end); + + if (end == null) continue; + + retval.Add(new WorkItemLinkInfo(result2[index].Source?.Id ?? 0, result2[index].Target?.Id ?? 0, end)); + } + + return retval.AsReadOnly(); + } + + private IEnumerable RunkLinkQueryImplLazy() { // Eager loading for the link type ID (which is not returned by the REST API) causes ~250ms delay @@ -114,26 +150,19 @@ private ReadOnlyCollection RunkLinkQueryImpl() // To avoid an enumerator allocation we are forcing the cast var result2 = (List)result.WorkItemRelations; - var retval = new List(result2.Count + 1); for (var index = 0; index < result2.Count; index++) { // REVIEW: Closure allocation: workItemLink + ends outer closure - var workItemLink = result2[index]; - IWorkItemLinkTypeEnd EndValueFactory() { - return ends.Value.TryGetByName(workItemLink.Rel, out IWorkItemLinkTypeEnd end) ? end : null; + return ends.Value.TryGetByName(result2[index].Rel, out IWorkItemLinkTypeEnd end) ? end : null; } - retval.Add( - new WorkItemLinkInfo( - workItemLink.Source?.Id ?? 0, - workItemLink.Target?.Id ?? 0, - new Lazy(EndValueFactory))); - } + var ltEnd = new Lazy(EndValueFactory); - return retval.AsReadOnly(); + yield return new WorkItemLinkInfo(result2[index].Source?.Id ?? 0, result2[index].Target?.Id ?? 0, ltEnd); + } } [NotNull] @@ -157,13 +186,23 @@ private List RunQueryImpl() if (_ids == null) return EmptyWorkItems; var retval = new List(_ids.Count); + var t = new CancellationToken(); + var ts = + new List>>( + _ids.Count + % _workItemStore.Configuration.PageSize + + 1); + var qry = _ids.Partition(_workItemStore.Configuration.PageSize); + + var c = _workItemStore.NativeWorkItemStore.Value; + var o = _workItemStore.Configuration; - var ts = new List>>(_ids.Count % _workItemStore.PageSize + 1); - var qry = _ids.Partition(_workItemStore.PageSize); foreach (var s in qry) - ts.Add(_workItemStore.NativeWorkItemStore.Value.GetWorkItemsAsync(s, null, _asOf, Expand, WorkItemErrorPolicy.Omit)); + { + ts.Add(c.GetWorkItemsAsync(s, null, _asOf, o.WorkItemExpand, o.WorkItemErrorPolicy, null, t)); + } - var results = Task.WhenAll(ts).GetAwaiter().GetResult(); + var results = Task.WhenAll(ts.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); // This is done in parallel so keep performance similar to the SOAP client for (var i = 0; i < results.Length; i++) @@ -174,23 +213,69 @@ private List RunQueryImpl() { var workItem = workItemsPartition[j]; - // REIVEW: Allocate for WorkItem reference type - var wi = new WorkItem( - workItem, - // REVIEW: Allocate for reference type - _workItemStore - .Projects[(string)workItem.Fields[CoreFieldRefNames.TeamProject]] - .WorkItemTypes[(string)workItem.Fields[CoreFieldRefNames.WorkItemType]], - // REVIEW: Delegate allocation from method group - LinkFunc).AsProxy(); - - retval.Add(wi); + var wi = _workItemStore.Configuration.LazyLoadingEnabled ? CreateItemLazy(workItem) : CreateItemEager(workItem); + + retval.Add(_workItemStore.Configuration.ProxyCreationEnabled ? wi.AsProxy() : wi); } } return retval; } + [NotNull] + private IEnumerable RunQueryImplLazy() + { + Contract.Ensures(Contract.Result>() != null); + + if (_ids == null && _query != null) + { + var result = _workItemStore.NativeWorkItemStore.Value.QueryByWiqlAsync(_query, _timePrecision).GetAwaiter().GetResult(); + if (!result.WorkItems.Any()) yield break; + _ids = new HashSet(); + var items = (List)result.WorkItems; + for (var i = 0; i < items.Count; i++) + { + var wir = items[i]; + _ids.Add(wir.Id); + } + } + + if (_ids == null) yield break; + + var t = new CancellationToken(); + var ts = + new List>>( + _ids.Count + % _workItemStore.Configuration.PageSize + + 1); + var qry = _ids.Partition(_workItemStore.Configuration.PageSize); + var o = _workItemStore.Configuration; + var c = _workItemStore.NativeWorkItemStore.Value; + var f = o.WorkItemExpand == WorkItemExpand.Fields || o.WorkItemExpand == WorkItemExpand.All ? null : o.DefaultFields; + + foreach (var s in qry) + { + ts.Add(c.GetWorkItemsAsync(s, f, _asOf, o.WorkItemExpand, o.WorkItemErrorPolicy, null, t)); + } + + var results = Task.WhenAll(ts.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); + + // This is done in parallel so keep performance similar to the SOAP client + for (var i = 0; i < results.Length; i++) + { + var workItemsPartition = results[i]; + // REIVEW: Allocate for workItem variable + for (var j = 0; j < workItemsPartition.Count; j++) + { + var workItem = workItemsPartition[j]; + + var wi = _workItemStore.Configuration.LazyLoadingEnabled ? CreateItemLazy(workItem) : CreateItemEager(workItem); + + yield return _workItemStore.Configuration.ProxyCreationEnabled ? wi.AsProxy() : wi; + } + } + } + private WorkItemLinkTypeEndCollection WorkItemLinkTypeEndValueFactory() { return (WorkItemLinkTypeEndCollection)GetLinkTypes(); diff --git a/src/Qwiq.Core.Rest/QueryFactory.cs b/src/Qwiq.Core.Rest/QueryFactory.cs index 7a8b2a43..a260aaa2 100644 --- a/src/Qwiq.Core.Rest/QueryFactory.cs +++ b/src/Qwiq.Core.Rest/QueryFactory.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Globalization; +using JetBrains.Annotations; + using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; namespace Microsoft.Qwiq.Client.Rest @@ -10,8 +13,10 @@ internal class QueryFactory : IQueryFactory { private readonly WorkItemStore _store; - private QueryFactory(WorkItemStore store) + private QueryFactory([NotNull] WorkItemStore store) { + Contract.Requires(store != null); + _store = store ?? throw new ArgumentNullException(nameof(store)); } @@ -29,7 +34,7 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) { // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). // It is used for ASOF - FormattableString ws = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; + FormattableString ws = $"SELECT {string.Join(", ", _store.Configuration.DefaultFields ?? new[] { CoreFieldRefNames.Id })} FROM WorkItems"; var wiql = ws.ToString(CultureInfo.InvariantCulture); if (asOf.HasValue) @@ -46,8 +51,10 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) return Create(ids, wiql); } - public static IQueryFactory GetInstance(WorkItemStore store) + public static IQueryFactory GetInstance([NotNull] WorkItemStore store) { + Contract.Requires(store != null); + return new QueryFactory(store); } } diff --git a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj index 30270d62..eb4c7476 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj @@ -95,6 +95,7 @@ + diff --git a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs index 2eb5dfb8..b8c3a8a7 100644 --- a/src/Qwiq.Core.Rest/TfsConnectionFactory.cs +++ b/src/Qwiq.Core.Rest/TfsConnectionFactory.cs @@ -16,6 +16,12 @@ private TfsConnectionFactory() protected override ITeamProjectCollection ConnectToTfsCollection(Uri endpoint, VssCredentials credentials) { var tfsServer = new VssConnection(endpoint, credentials); + + tfsServer.Settings.BypassProxyOnLocal = true; + tfsServer.Settings.CompressionEnabled = true; + + + tfsServer.ConnectAsync(VssConnectMode.Automatic).GetAwaiter().GetResult(); if (!tfsServer.HasAuthenticated) throw new InvalidOperationException("Could not connect."); return tfsServer.AsProxy(); diff --git a/src/Qwiq.Core.Rest/WorkItem.cs b/src/Qwiq.Core.Rest/WorkItem.cs index df0e0e00..61c9bfaa 100644 --- a/src/Qwiq.Core.Rest/WorkItem.cs +++ b/src/Qwiq.Core.Rest/WorkItem.cs @@ -23,6 +23,8 @@ internal class WorkItem : Qwiq.WorkItem [CanBeNull] private LinkCollection _links; + private Uri _uri; + public WorkItem( [NotNull] TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, [NotNull] IWorkItemType wit, @@ -35,7 +37,21 @@ public WorkItem( _item = item ?? throw new ArgumentNullException(nameof(item)); _linkFunc = linkFunc ?? throw new ArgumentNullException(nameof(linkFunc)); Url = _item.Url; - Uri = new Uri(_item.Url, UriKind.Absolute); + _uri = new Uri(_item.Url, UriKind.Absolute); + } + + public WorkItem( + TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item, + Lazy wit, + Func linkFunc) + : base(wit) + { + Contract.Requires(item != null); + Contract.Requires(wit != null); + Contract.Requires(linkFunc != null); + _item = item ?? throw new ArgumentNullException(nameof(item)); + _linkFunc = linkFunc ?? throw new ArgumentNullException(nameof(linkFunc)); + Url = _item.Url; } public override int AttachedFileCount @@ -118,7 +134,7 @@ public override int RelatedLinkCount public override int Rev => _item.Rev.GetValueOrDefault(0); - public override Uri Uri { get; } + public override Uri Uri => _uri ?? (_uri = new Uri(_item.Url, UriKind.Absolute)); public override string Url { get; } diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index db3492d2..4dfb1951 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -12,13 +12,8 @@ namespace Microsoft.Qwiq.Client.Rest { internal class WorkItemStore : IWorkItemStore { - private static readonly Regex ImmutableLinkTypeNameRegex = new Regex( - "(?.*)-(?.*)", - RegexOptions.Singleline | RegexOptions.Compiled); - - private IWorkItemLinkTypeCollection _linkTypes; - - private IProjectCollection _projects; + private static readonly Regex ImmutableLinkTypeNameRegex = + new Regex("(?.*)-(?.*)", RegexOptions.Singleline | RegexOptions.Compiled); private readonly Lazy _queryFactory; @@ -26,19 +21,19 @@ internal class WorkItemStore : IWorkItemStore private IFieldDefinitionCollection _fieldDefinitions; - internal WorkItemStore( - Func tpcFactory, - Func queryFactory, - int pageSize = Rest.Query.MaximumBatchSize) - : this(tpcFactory, () => tpcFactory()?.GetClient(), queryFactory, pageSize) + private IWorkItemLinkTypeCollection _linkTypes; + + private IProjectCollection _projects; + + internal WorkItemStore(Func tpcFactory, Func queryFactory) + : this(tpcFactory, () => tpcFactory()?.GetClient(), queryFactory) { } internal WorkItemStore( Func tpcFactory, Func wisFactory, - Func queryFactory, - int pageSize = Rest.Query.MaximumBatchSize) + Func queryFactory) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); @@ -46,61 +41,35 @@ internal WorkItemStore( _tfs = new Lazy(tpcFactory); NativeWorkItemStore = new Lazy(wisFactory); _queryFactory = new Lazy(() => queryFactory(this)); - - // Boundary check the batch size - - if (pageSize < Rest.Query.MinimumBatchSize || pageSize > Rest.Query.MaximumBatchSize) throw new PageSizeRangeException(); - - - PageSize = pageSize; - - - - - + Configuration = new WorkItemStoreConfiguration(); } - private WorkItemLinkTypeCollection WorkItemLinkTypeCollectionFactory() - { - return GetLinks(NativeWorkItemStore.Value); - } - - private IProjectCollection ProjectCollectionFactory() - { - using (var projectHttpClient = _tfs.Value.GetClient()) - { - var projects = (List)projectHttpClient.GetProjects(ProjectState.All).GetAwaiter().GetResult(); - var projects2 = new List(projects.Count + 1); + public VssCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; - for (var i = 0; i < projects.Count; i++) - { - var project = projects[i]; - var p = new Project(project, this); - projects2.Add(p); - } + public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection.AuthorizedIdentity; - return new ProjectCollection(projects2); - } - } + /// + Qwiq.WorkItemStoreConfiguration IWorkItemStore.Configuration => Configuration; - public int PageSize { get; } + public WorkItemStoreConfiguration Configuration { get;} - internal Lazy NativeWorkItemStore { get; } + public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions + ?? (_fieldDefinitions = new FieldDefinitionCollection(this)); - public VssCredentials AuthorizedCredentials => TeamProjectCollection.AuthorizedCredentials; - public IFieldDefinitionCollection FieldDefinitions => _fieldDefinitions ?? (_fieldDefinitions = new FieldDefinitionCollection(this)); public IProjectCollection Projects => _projects ?? (_projects = ProjectCollectionFactory()); - public ITeamProjectCollection TeamProjectCollection => _tfs.Value; + public IRegisteredLinkTypeCollection RegisteredLinkTypes { get; } - public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection.AuthorizedIdentity; + public ITeamProjectCollection TeamProjectCollection => _tfs.Value; public TimeZone TimeZone => TeamProjectCollection?.TimeZone ?? TimeZone.CurrentTimeZone; public IWorkItemLinkTypeCollection WorkItemLinkTypes => _linkTypes ?? (_linkTypes = WorkItemLinkTypeCollectionFactory()); + internal Lazy NativeWorkItemStore { get; } + public void Dispose() { Dispose(true); @@ -137,8 +106,6 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision return query.RunLinkQuery(); } - public IRegisteredLinkTypeCollection RegisteredLinkTypes { get; } - private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient workItemStore) { var types = workItemStore.GetRelationTypesAsync().GetAwaiter().GetResult(); @@ -174,33 +141,32 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo type.SetForwardEnd(new WorkItemLinkTypeEnd(forwardEnd) { IsForwardLink = true, LinkType = type }); type.SetReverseEnd( - type.IsDirectional - ? new WorkItemLinkTypeEnd( - ends.SingleOrDefault(p => p.ReferenceName.EndsWith("Reverse"))) { LinkType = type } - : type.ForwardEnd); + type.IsDirectional + ? new WorkItemLinkTypeEnd( + ends.SingleOrDefault(p => p.ReferenceName.EndsWith("Reverse"))) + { + LinkType + = type + } + : type.ForwardEnd); // The REST API does not return the ID of the link type. For well-known system links, we can populate the ID value if (CoreLinkTypeReferenceNames.All.Contains(type.ReferenceName, StringComparer.OrdinalIgnoreCase)) { - int forwardId = 0, reverseId = 0; + int forwardId = 0, + reverseId = 0; - if (CoreLinkTypeReferenceNames.Hierarchy.Equals( - type.ReferenceName, - StringComparison.OrdinalIgnoreCase)) + if (CoreLinkTypeReferenceNames.Hierarchy.Equals(type.ReferenceName, StringComparison.OrdinalIgnoreCase)) { // The forward should be Child, but the ID used in CoreLinkTypes is -2, should be 2 forwardId = CoreLinkTypes.Child; reverseId = CoreLinkTypes.Parent; } - else if (CoreLinkTypeReferenceNames.Related.Equals( - type.ReferenceName, - StringComparison.OrdinalIgnoreCase)) + else if (CoreLinkTypeReferenceNames.Related.Equals(type.ReferenceName, StringComparison.OrdinalIgnoreCase)) { forwardId = reverseId = CoreLinkTypes.Related; } - else if (CoreLinkTypeReferenceNames.Dependency.Equals( - type.ReferenceName, - StringComparison.OrdinalIgnoreCase)) + else if (CoreLinkTypeReferenceNames.Dependency.Equals(type.ReferenceName, StringComparison.OrdinalIgnoreCase)) { forwardId = CoreLinkTypes.Successor; reverseId = CoreLinkTypes.Predecessor; @@ -218,5 +184,28 @@ private void Dispose(bool disposing) { if (disposing) if (NativeWorkItemStore.IsValueCreated) NativeWorkItemStore.Value?.Dispose(); } + + private IProjectCollection ProjectCollectionFactory() + { + using (var projectHttpClient = _tfs.Value.GetClient()) + { + var projects = (List)projectHttpClient.GetProjects(ProjectState.WellFormed).GetAwaiter().GetResult(); + var projects2 = new List(projects.Count + 1); + + for (var i = 0; i < projects.Count; i++) + { + var project = projects[i]; + var p = new Project(project, this); + projects2.Add(p); + } + + return new ProjectCollection(projects2); + } + } + + private WorkItemLinkTypeCollection WorkItemLinkTypeCollectionFactory() + { + return GetLinks(NativeWorkItemStore.Value); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs b/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs new file mode 100644 index 00000000..580fb8ab --- /dev/null +++ b/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Diagnostics; + +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; + +namespace Microsoft.Qwiq.Client.Rest +{ + public class WorkItemStoreConfiguration : Qwiq.WorkItemStoreConfiguration + { + private WorkItemExpand _workItemExpand; + + private IEnumerable _defaultFields; + + private static readonly string[] DefaultFieldValue = { + CoreFieldRefNames.AssignedTo, + CoreFieldRefNames.ChangedBy, + CoreFieldRefNames.ChangedDate, + CoreFieldRefNames.Id, + CoreFieldRefNames.State, + CoreFieldRefNames.TeamProject, + CoreFieldRefNames.Title, + CoreFieldRefNames.WorkItemType + }; + + internal WorkItemStoreConfiguration() + { + WorkItemExpand = WorkItemExpand.All; + WorkItemErrorPolicy = WorkItemErrorPolicy.Omit; + DefaultFields = null; + } + + /// + /// Gets or sets a value indicating which fields to load when querying for a work item. Has no effect if + /// is set to or + /// . Defaults to + /// , , + /// , , + /// , + /// + public IEnumerable DefaultFields + { + get { return _defaultFields; } + set + { + if (WorkItemExpand != WorkItemExpand.None) + { + Trace.TraceWarning($"The {nameof(WorkItemExpand)} parameter can not be used with the {nameof(DefaultFields)} parameter."); + _workItemExpand = WorkItemExpand.None; + } + _defaultFields = value; + } + } + + /// + /// Gets or sets a value indicating the expected behavior when querying for a work item that does not exist. Default + /// value is + /// + public WorkItemErrorPolicy WorkItemErrorPolicy { get; set; } + + /// + /// Gets or sets a value indicating which sub-elements of + /// to expand. Default value is + /// . + /// + public WorkItemExpand WorkItemExpand + { + get { return _workItemExpand; } + set + { + _workItemExpand = value; + if (value != WorkItemExpand.None) + { + Trace.TraceWarning( + $"The {nameof(WorkItemExpand)} parameter can not be used with the {nameof(DefaultFields)} parameter."); + _defaultFields = null; + } + else + { + _defaultFields = DefaultFieldValue; + } + } + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index db7d5b4a..1921749b 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -6,7 +6,6 @@ using JetBrains.Annotations; using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.WorkItemTracking.Common; namespace Microsoft.Qwiq.Client.Soap { @@ -16,18 +15,14 @@ internal class Query : IQuery private readonly TeamFoundation.WorkItemTracking.Client.Query _query; - internal Query([NotNull] TeamFoundation.WorkItemTracking.Client.Query query, int pageSize = PageSizeLimits.DefaultPageSize) + internal Query([NotNull] TeamFoundation.WorkItemTracking.Client.Query query, int pageSize) { Contract.Requires(query != null); - Contract.Requires(pageSize < PageSizeLimits.MaxPageSize); - Contract.Requires(pageSize > PageSizeLimits.DefaultPageSize); - + _query = query; _pageSize = pageSize; - _query = query ?? throw new ArgumentNullException(nameof(query)); - - if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) throw new PageSizeRangeException(); } + [CanBeNull] private IWorkItemLinkTypeEndCollection _linkTypes; public IWorkItemLinkTypeEndCollection GetLinkTypes() @@ -51,18 +46,21 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() public IEnumerable RunLinkQuery() { // REVIEW: Create an IWorkItemLinkInfo like IWorkItemLinkTypeEndCollection and IWorkItemCollection + var wili = _query.RunLinkQuery(); + var retval = new List(wili.Length); + var lt = GetLinkTypes().ToDictionary(k=>k.Id, e=>e); + for (var i = 0; i < wili.Length; i++) + { + if (wili[i].LinkTypeId == 0) continue; + // TODO: Use Lazy config options - return _query.RunLinkQuery() - .Select( - item => - { - IWorkItemLinkTypeEnd LinkTypeEndFactory() => GetLinkTypes().TryGetById(item.LinkTypeId, out IWorkItemLinkTypeEnd end) ? end : null; - return new WorkItemLinkInfo(item.SourceId, item.TargetId, new Lazy(LinkTypeEndFactory)); - }) - .ToList() - .AsReadOnly(); + var lte = lt[wili[i].LinkTypeId]; + retval.Add(new WorkItemLinkInfo(wili[i].SourceId, wili[i].TargetId, lte)); + } + + return retval.AsReadOnly(); } public IWorkItemCollection RunQuery() @@ -70,9 +68,11 @@ public IWorkItemCollection RunQuery() var wic = _query.RunQuery(); wic.PageSize = _pageSize; + // TODO: Use Lazy config options var items = new List(wic.Count); for (var i = 0; i < wic.Count; i++) { + // TODO: Use proxy config options var item = ExceptionHandlingDynamicProxyFactory.Create((WorkItem)wic[i]); items.Add(item); } diff --git a/src/Qwiq.Core.Soap/QueryFactory.cs b/src/Qwiq.Core.Soap/QueryFactory.cs index 19030425..0ec4c88a 100644 --- a/src/Qwiq.Core.Soap/QueryFactory.cs +++ b/src/Qwiq.Core.Soap/QueryFactory.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; +using JetBrains.Annotations; + using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.WorkItemTracking.Common; @@ -9,23 +12,25 @@ namespace Microsoft.Qwiq.Client.Soap { internal class QueryFactory : IQueryFactory { + [NotNull] private readonly WorkItemStore _store; - internal QueryFactory(WorkItemStore store) + internal QueryFactory([NotNull] WorkItemStore store) { + Contract.Requires(store != null); + _store = store ?? throw new ArgumentNullException(nameof(store)); } public IQuery Create(string wiql, bool dayPrecision) { - return ExceptionHandlingDynamicProxyFactory.Create( - new Query( - new TeamFoundation.WorkItemTracking.Client.Query( - _store.NativeWorkItemStore, - wiql, - null, - dayPrecision), - _store.PageSize)); + var q = new Query( + new TeamFoundation.WorkItemTracking.Client.Query(_store.NativeWorkItemStore, wiql, null, dayPrecision), + _store.Configuration.PageSize); + + return _store.Configuration.ProxyCreationEnabled + ? ExceptionHandlingDynamicProxyFactory.Create(q) + : q; } public IQuery Create(IEnumerable ids, string wiql) @@ -34,10 +39,13 @@ public IQuery Create(IEnumerable ids, string wiql) if (string.IsNullOrWhiteSpace(wiql)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(wiql)); - return ExceptionHandlingDynamicProxyFactory.Create( - new Query( - new TeamFoundation.WorkItemTracking.Client.Query(_store.NativeWorkItemStore, wiql, ids.ToArray()), - _store.PageSize)); + var q = new Query( + new TeamFoundation.WorkItemTracking.Client.Query(_store.NativeWorkItemStore, wiql, ids.ToArray()), + _store.Configuration.PageSize); + + return _store.Configuration.ProxyCreationEnabled + ? ExceptionHandlingDynamicProxyFactory.Create(q) + : q; } public IQuery Create(IEnumerable ids, DateTime? asOf = null) diff --git a/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs index 99dda41e..8bd3d859 100644 --- a/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -6,9 +9,11 @@ namespace Microsoft.Qwiq.Client.Soap { internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd { - internal WorkItemLinkTypeEnd(Tfs.WorkItemLinkTypeEnd end) - : base(end?.ImmutableName, new Lazy(() => new WorkItemLinkTypeEnd(end?.OppositeEnd))) + internal WorkItemLinkTypeEnd([NotNull] Tfs.WorkItemLinkTypeEnd end) + : base(end.ImmutableName, new Lazy(() => new WorkItemLinkTypeEnd(end.OppositeEnd))) { + Contract.Requires(end != null); + if (end == null) throw new ArgumentNullException(nameof(end)); Id = end.Id; LinkType = new WorkItemLinkType(end.LinkType); diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index 084c3690..cdf16917 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -1,5 +1,4 @@ using Microsoft.Qwiq.Exceptions; -using Microsoft.TeamFoundation.WorkItemTracking.Common; using Microsoft.VisualStudio.Services.Common; using System; using System.Collections.Generic; @@ -29,15 +28,12 @@ internal class WorkItemStore : IWorkItemStore internal WorkItemStore( Func tpcFactory, Func wisFactory, - Func queryFactory, - int pageSize = PageSizeLimits.MaxPageSize) + Func queryFactory) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (wisFactory == null) throw new ArgumentNullException(nameof(wisFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); - if (pageSize < PageSizeLimits.DefaultPageSize || pageSize > PageSizeLimits.MaxPageSize) throw new PageSizeRangeException(); - _tfs = new Lazy(tpcFactory); _workItemStore = new Lazy(wisFactory); _queryFactory = new Lazy(() => queryFactory(this)); @@ -62,14 +58,15 @@ IRegisteredLinkTypeCollection RegisteredLinkTypeCollectionFactory() _projects = new Lazy(() => new ProjectCollection(_workItemStore.Value.Projects)); - PageSize = pageSize; + Configuration = new WorkItemStoreConfiguration(); + + } internal WorkItemStore( Func tpcFactory, - Func queryFactory, - int pageSize = PageSizeLimits.MaxPageSize) - : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory, pageSize) + Func queryFactory) + : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory) { } @@ -84,7 +81,7 @@ internal WorkItemStore( .Value .FieldDefinitions)); - public int PageSize { get; } + public IProjectCollection Projects => _projects.Value; @@ -152,6 +149,9 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision } } + /// + public WorkItemStoreConfiguration Configuration { get; } + protected virtual void Dispose(bool disposing) { if (disposing) if (_tfs.IsValueCreated) _tfs.Value?.Dispose(); diff --git a/src/Qwiq.Core/IWorkItemStore.cs b/src/Qwiq.Core/IWorkItemStore.cs index 35ba0189..a2f153d8 100644 --- a/src/Qwiq.Core/IWorkItemStore.cs +++ b/src/Qwiq.Core/IWorkItemStore.cs @@ -85,5 +85,10 @@ public interface IWorkItemStore : IDisposable /// if set to true [day precision]. /// IEnumerable<IWorkItemLinkInfo>. IEnumerable QueryLinks(string wiql, bool dayPrecision = false); + + /// + /// Provides access to configuration options for this instance. + /// + WorkItemStoreConfiguration Configuration { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 0359adff..2325a9e7 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -208,6 +208,7 @@ + diff --git a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs index e04f011e..97d2b2a8 100644 --- a/src/Qwiq.Core/ReadOnlyCollectionWithId.cs +++ b/src/Qwiq.Core/ReadOnlyCollectionWithId.cs @@ -37,6 +37,13 @@ protected ReadOnlyObjectWithIdCollection([CanBeNull] List items) _mapById = new Dictionary(items?.Count ?? 0); } + protected ReadOnlyObjectWithIdCollection([CanBeNull] IEnumerable items) + :base(items) + { + _idFunc = a => a.Id; + _mapById = new Dictionary(); + } + public virtual bool Contains(TId id) { Ensure(); diff --git a/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs index d6ed0423..37864230 100644 --- a/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs +++ b/src/Qwiq.Core/ReadOnlyObjectWithNameCollection.cs @@ -27,13 +27,13 @@ public abstract class ReadOnlyObjectWithNameCollection : IReadOnlyObjectWithN private IDictionary _mapByName; - protected ReadOnlyObjectWithNameCollection([NotNull] Func> itemFactory, [NotNull] Func nameFunc) + protected ReadOnlyObjectWithNameCollection([NotNull] Func> itemFactory, [CanBeNull] Func nameFunc) { Contract.Requires(itemFactory != null); Contract.Requires(nameFunc != null); ItemFactory = itemFactory ?? throw new ArgumentNullException(nameof(itemFactory)); - _nameFunc = nameFunc ?? throw new ArgumentNullException(nameof(nameFunc)); + _nameFunc = nameFunc; } protected ReadOnlyObjectWithNameCollection([CanBeNull] List items, [CanBeNull] Func nameFunc) @@ -44,6 +44,11 @@ protected ReadOnlyObjectWithNameCollection([CanBeNull] List items, [CanBeNull _alreadyInit = false; } + protected ReadOnlyObjectWithNameCollection([CanBeNull] IEnumerable items) + : this(()=> items, null) + { + } + protected ReadOnlyObjectWithNameCollection([CanBeNull] List items) : this(items, null) { @@ -131,6 +136,12 @@ public virtual int IndexOf(T value) public virtual bool TryGetByName(string name, out T value) { + if (string.IsNullOrEmpty(name)) + { + value = default(T); + return false; + } + Ensure(); if (_mapByName.TryGetValue(name, out int num)) { diff --git a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs index 0849b23e..df1cc5e0 100644 --- a/src/Qwiq.Core/RegisteredLinkTypeCollection.cs +++ b/src/Qwiq.Core/RegisteredLinkTypeCollection.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq { public class RegisteredLinkTypeCollection : ReadOnlyObjectWithNameCollection, IRegisteredLinkTypeCollection { - public RegisteredLinkTypeCollection(List linkTypes) + internal RegisteredLinkTypeCollection(List linkTypes) : base(linkTypes, type => type.Name) { } diff --git a/src/Qwiq.Core/RelatedLink.cs b/src/Qwiq.Core/RelatedLink.cs index 9c723355..fc88e2f1 100644 --- a/src/Qwiq.Core/RelatedLink.cs +++ b/src/Qwiq.Core/RelatedLink.cs @@ -1,11 +1,13 @@ using System; using System.Diagnostics; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class RelatedLink : Link, IRelatedLink { - internal RelatedLink(int related, IWorkItemLinkTypeEnd linkTypeEnd = null, string comment = null) + internal RelatedLink(int related, [CanBeNull] IWorkItemLinkTypeEnd linkTypeEnd = null, [CanBeNull] string comment = null) : base(comment, BaseLinkType.RelatedLink) { RelatedWorkItemId = related; diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index c963e502..7887bff6 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -13,6 +13,7 @@ namespace Microsoft.Qwiq /// public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable { + [CanBeNull] private readonly IWorkItemType _type; private bool _useFields = true; @@ -22,6 +23,9 @@ public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable _fieldFactory; + [CanBeNull] + private readonly Lazy _lazyType; + protected internal WorkItem([NotNull] IWorkItemType type, [CanBeNull] Dictionary fields) : base(fields) { @@ -38,6 +42,12 @@ protected internal WorkItem([NotNull] IWorkItemType type) } + protected internal WorkItem([NotNull] Lazy type) + { + Contract.Requires(type != null); + _lazyType = type; + } + protected internal WorkItem([NotNull] IWorkItemType type, [NotNull] Func fieldCollectionFactory) { @@ -110,7 +120,7 @@ public virtual string Keywords public virtual IEnumerable Revisions => throw new NotSupportedException(); - public virtual IWorkItemType Type => _type ?? throw new NotSupportedException(); + public virtual IWorkItemType Type => _type ?? _lazyType?.Value ?? throw new NotSupportedException(); public abstract Uri Uri { get; } @@ -221,5 +231,11 @@ public override int GetHashCode() { return WorkItemComparer.Default.GetHashCode(this); } + + /// + public override string ToString() + { + return $"{WorkItemType} {Id} {Title}"; + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCollection.cs b/src/Qwiq.Core/WorkItemCollection.cs index a786beba..6a96d017 100644 --- a/src/Qwiq.Core/WorkItemCollection.cs +++ b/src/Qwiq.Core/WorkItemCollection.cs @@ -5,7 +5,12 @@ namespace Microsoft.Qwiq { public class WorkItemCollection : ReadOnlyObjectWithIdCollection, IWorkItemCollection { - public WorkItemCollection(List workItems) + internal WorkItemCollection([CanBeNull] List workItems) + : base(workItems) + { + } + + internal WorkItemCollection([CanBeNull] IEnumerable workItems) : base(workItems) { } diff --git a/src/Qwiq.Core/WorkItemLinkInfo.cs b/src/Qwiq.Core/WorkItemLinkInfo.cs index f766b841..2f0f400d 100644 --- a/src/Qwiq.Core/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core/WorkItemLinkInfo.cs @@ -10,27 +10,27 @@ namespace Microsoft.Qwiq public class WorkItemLinkInfo : IWorkItemLinkInfo { [CanBeNull] - private readonly IWorkItemLinkTypeEnd _id; + private readonly IWorkItemLinkTypeEnd _linkTypeEnd; [CanBeNull] - private readonly Lazy _lazyId; + private readonly Lazy _lazyLinkTypeEnd; - internal WorkItemLinkInfo(int sourceId, int targetId, [NotNull] IWorkItemLinkTypeEnd id) + internal WorkItemLinkInfo(int sourceId, int targetId, [NotNull] IWorkItemLinkTypeEnd linkTypeEnd) : this(sourceId, targetId, (Lazy) null) { - Contract.Requires(id != null); - - _id = id ?? throw new ArgumentNullException(nameof(id)); + Contract.Requires(linkTypeEnd != null); + + _linkTypeEnd = linkTypeEnd ?? throw new ArgumentNullException(nameof(linkTypeEnd)); } - internal WorkItemLinkInfo(int sourceId, int targetId, [CanBeNull] Lazy lazyId) + internal WorkItemLinkInfo(int sourceId, int targetId, [CanBeNull] Lazy linkTypeEnd) { SourceId = sourceId; TargetId = targetId; - _lazyId = lazyId; + _lazyLinkTypeEnd = linkTypeEnd; } - public IWorkItemLinkTypeEnd LinkType => _id ?? _lazyId?.Value ?? throw new InvalidOperationException(); + public IWorkItemLinkTypeEnd LinkType => _linkTypeEnd ?? _lazyLinkTypeEnd?.Value ?? throw new InvalidOperationException(); public int SourceId { get; } diff --git a/src/Qwiq.Core/WorkItemLinkType.cs b/src/Qwiq.Core/WorkItemLinkType.cs index a2ee48eb..38377ec0 100644 --- a/src/Qwiq.Core/WorkItemLinkType.cs +++ b/src/Qwiq.Core/WorkItemLinkType.cs @@ -1,6 +1,9 @@ using System; +using System.Diagnostics.Contracts; using System.Globalization; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class WorkItemLinkType : IWorkItemLinkType, IEquatable @@ -13,27 +16,38 @@ public class WorkItemLinkType : IWorkItemLinkType, IEquatable private IWorkItemLinkTypeEnd _reverse; - internal WorkItemLinkType(string referenceName, IWorkItemLinkTypeEnd forward, IWorkItemLinkTypeEnd reverse) + internal WorkItemLinkType([NotNull] string referenceName, [NotNull] IWorkItemLinkTypeEnd forward, [NotNull] IWorkItemLinkTypeEnd reverse) : this(referenceName) { + Contract.Requires(!string.IsNullOrEmpty(referenceName)); + Contract.Requires(forward != null); + Contract.Requires(reverse != null); + _forward = forward ?? throw new ArgumentNullException(nameof(forward)); _reverse = reverse ?? throw new ArgumentNullException(nameof(reverse)); _forwardFac = null; _reverseFac = null; } - internal WorkItemLinkType(string referenceName, Lazy forward, Lazy reverse) + internal WorkItemLinkType([NotNull] string referenceName, [NotNull] Lazy forward, [NotNull] Lazy reverse) : this(referenceName) { + Contract.Requires(!string.IsNullOrEmpty(referenceName)); + Contract.Requires(forward != null); + Contract.Requires(reverse != null); + _forwardFac = forward ?? throw new ArgumentNullException(nameof(forward)); _reverseFac = reverse ?? throw new ArgumentNullException(nameof(reverse)); } - internal WorkItemLinkType(string referenceName) + internal WorkItemLinkType([NotNull] string referenceName) { - ReferenceName = referenceName; + Contract.Requires(!string.IsNullOrEmpty(referenceName)); + if (string.IsNullOrWhiteSpace(referenceName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); + + ReferenceName = string.Intern(referenceName); } public IWorkItemLinkTypeEnd ForwardEnd => CoerceForwardValue(); diff --git a/src/Qwiq.Core/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs index 1d259424..73da01c3 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs @@ -1,24 +1,44 @@ using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; namespace Microsoft.Qwiq { public class WorkItemLinkTypeEnd : IWorkItemLinkTypeEnd, IEquatable { - private readonly Lazy _opposite; - internal WorkItemLinkTypeEnd(string immutableName, Lazy oppositeEnd) + private IWorkItemLinkTypeEnd _oppositeEnd; + + [CanBeNull] + private readonly Lazy _lazyOpposite; + + internal WorkItemLinkTypeEnd([NotNull] string immutableName, [NotNull] IWorkItemLinkTypeEnd oppositeEnd) + { + Contract.Requires(!string.IsNullOrEmpty(immutableName)); + Contract.Requires(oppositeEnd != null); + _oppositeEnd = oppositeEnd ?? throw new ArgumentNullException(nameof(oppositeEnd)); + } + + internal WorkItemLinkTypeEnd([NotNull] string immutableName, [NotNull] Lazy oppositeEnd) : this(immutableName) { - _opposite = oppositeEnd; + Contract.Requires(!string.IsNullOrEmpty(immutableName)); + Contract.Requires(oppositeEnd != null); + + _lazyOpposite = oppositeEnd ?? throw new ArgumentNullException(nameof(oppositeEnd)); } - internal WorkItemLinkTypeEnd(string immutableName) + internal WorkItemLinkTypeEnd([NotNull] string immutableName) { - ImmutableName = immutableName; + Contract.Requires(!string.IsNullOrEmpty(immutableName)); if (string.IsNullOrWhiteSpace(immutableName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(immutableName)); - _opposite = new Lazy(() => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); + + ImmutableName = string.Intern(immutableName); + + _lazyOpposite = new Lazy(() => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); } public int Id { get; internal set; } @@ -31,7 +51,7 @@ internal WorkItemLinkTypeEnd(string immutableName) public string Name { get; internal set; } - public IWorkItemLinkTypeEnd OppositeEnd => _opposite.Value; + public IWorkItemLinkTypeEnd OppositeEnd => _oppositeEnd ?? (_oppositeEnd = _lazyOpposite.Value); public bool Equals(IWorkItemLinkTypeEnd other) { diff --git a/src/Qwiq.Core/WorkItemStoreConfiguration.cs b/src/Qwiq.Core/WorkItemStoreConfiguration.cs new file mode 100644 index 00000000..effe5c5d --- /dev/null +++ b/src/Qwiq.Core/WorkItemStoreConfiguration.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.Contracts; + +namespace Microsoft.Qwiq +{ + public class WorkItemStoreConfiguration + { + private int _pageSize; + + internal WorkItemStoreConfiguration() + { + LazyLoadingEnabled = true; + ProxyCreationEnabled = true; + PageSize = 50; + } + + /// + /// Gets or sets a value indicating whether lazy loading of certain properties is enabled. Lazy loading is enabled by default + /// + public bool LazyLoadingEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether or not the framework will create instances of dynamically generated proxy classes whenever it creates an instance of a type. Note that even if proxy creation is enabled with this flag, proxy instances will only be created for entity types that meet the requirements for being proxied. Proxy creation is enabled by default. + /// + public bool ProxyCreationEnabled { get;set;} + + /// + /// Gets or sets a value indicating the number of work items to be returned in a page. Value is 50 by default. + /// + public int PageSize + + { + get => _pageSize; + set + { + const int MinimumBatchSize = 50; + const int MaximumBatchSize = 200; + + Contract.Requires(value >= MinimumBatchSize); + Contract.Requires(value <= MaximumBatchSize); + + + if (value < MinimumBatchSize || value > MaximumBatchSize) throw new PageSizeRangeException(); + + + _pageSize = value; + } + } + } +} diff --git a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs index 38fb8e8d..7a6d065c 100644 --- a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs @@ -62,9 +62,9 @@ IIdentityValueConverter identityValueConverter /// The work item mapper. public override void Map(Type targeWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { - var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Default); - if (!workingSet.Any()) return; + if (!workItemMappings.Any()) return; + var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Default); var validIdentityProperties = GetWorkItemIdentityFieldNameToIdentityPropertyMap(targeWorkItemType, _inspector); if (!validIdentityProperties.Any())return; diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 766a13f8..fbee51b4 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -27,9 +27,6 @@ Properties\AssemblyInfo.Common.cs - - Properties\ReSharper.Annotations.cs - diff --git a/test/Qwiq.Integration.Tests/IntegrationSettings.cs b/test/Qwiq.Integration.Tests/IntegrationSettings.cs index 80d21276..ee5b2efc 100644 --- a/test/Qwiq.Integration.Tests/IntegrationSettings.cs +++ b/test/Qwiq.Integration.Tests/IntegrationSettings.cs @@ -31,14 +31,25 @@ public static class IntegrationSettings public static Func CreateRestStore { get; } = () => { var options = AuthenticationOptions; - return Client.Rest.WorkItemStoreFactory.Default.Create(options); + var wis = Client.Rest.WorkItemStoreFactory.Default.Create(options); + Configure(wis); + return wis; }; + private static void Configure(IWorkItemStore wis) + { + wis.Configuration.PageSize = 200; + wis.Configuration.LazyLoadingEnabled = true; + wis.Configuration.ProxyCreationEnabled = true; + } + /// public static Func CreateSoapStore { get; } = () => { var options = AuthenticationOptions; - return Client.Soap.WorkItemStoreFactory.Default.Create(options); + var wis = Client.Soap.WorkItemStoreFactory.Default.Create(options); + Configure(wis); + return wis; }; /// diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs index 1dc88591..7de917b7 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs @@ -7,11 +7,23 @@ namespace Microsoft.Qwiq.WorkItemStore { + [TestClass] + public class LargeWiqlHierarchyQueryTests_EagerLoad : LargeWiqlHierarchyQueryTests + { + /// + public override void Given() + { + base.Given(); + Soap.Configuration.LazyLoadingEnabled = false; + Rest.Configuration.LazyLoadingEnabled = false; + } + } + [TestClass] public class LargeWiqlHierarchyQueryTests : WorkItemStoreComparisonContextSpecification { internal const string WIQL = @" -SELECT [System.Id] +SELECT [System.Id], [System.Title] FROM WorkItemLinks WHERE Source.[System.Id] IN (306417, 3135398, 3249369, 3492942, 4233618, 4708876, 4774628, 4774633, 5081979, 5145876, 5321076, 5347847, 5348414, 5399017, 5399125, 5399142, 5437042, 5580845, 5696165, 5755004, 5779431, 5791052, 5810594, 5814045, 6190723, 6340710, 6418471, 6420861, 6507248, 6966852, 7147489, 7358985, 7418008, 7462755, 7529329, 7547051, 7547912, 7578388, 7726674, 7734222, 7734226, 7734230, 7734245, 7734252, 7734254, 7734255, 7734260, 7734261, 7734269, 7802385, 7825004, 7881302, 7899054, 7899207, 7899214, 7899226, 7925414, 7978728, 7989565, 8040116, 8062597, 8083468, 8122369, 8168698, 8169478, 8169633, 8172195, 8219936, 8233267, 8240155, 8249707, 8256930, 8256939, 8256943, 8256947, 8273089, 8273096, 8274155, 8285368, 8291862, 8305945, 8311776, 8329534, 8402025, 8415967, 8461788, 8461811, 8464765, 8478988, 8479926, 8480154, 8480258, 8521180, 8523040, 8584490, 8647508, 8704880, 8909592, 9067373, 9197486, 9197751, 9256654, 9279773, 9403332, 9405201, 9416107, 9445547, 9451633, 9492546, 9637927, 9644862, 9647371, 9780582, 9796760, 9831841, 9836943, 9853813, 9941648, 10397497, 10397521, 10398675, 10448096, 10471325, 10499131, 10513555, 10527610, 10530854, 10552224, 10562556, 10588201, 10597347, 10597392, 10621199, 10621202, 10629409, 10650244, 10693987, 10696618, 10726461, 10726528, 10726538, 10726549, 10726569, 10726584, 10726595, 10726623, 10726641, 10731011, 10732765, 10748682, 10753466, 10754027, 10757138, 10760613, 10760657, 10760716, 10760745, 10760797, 10761005, 10762808, 10764882, 10769936, 10769966, 10769985, 10770045, 10770155, 10772744, 10781600, 10781696, 10782702, 10783490, 10784489, 10785622, 10786336, 10786381, 10788814, 10788938, 10789724, 10789737, 10795841, 10796505, 10796527, 10796531, 10796651, 10797459, 10799250, 10799746, 10802354, 10802569, 10803640, 10803778, 10804396, 10804706, 10825656, 10833432, 10834702, 10835610, 10835947, 10854312, 10872890, 10872990, 10875938, 10887469, 10888050, 10889671, 10893905, 10919153, 10940803, 10940927, 10955448, 10955522, 10956000, 10956061, 10956625, 10961487, 10961734, 10961908, 10983282, 10984200, 10991608, 11013506, 11017539, 11018019, 11018123, 11018139, 11018157, 11018183, 11018222, 11018336, 11053481, 11057194, 11070906, 11076205, 11084268, 11084305, 11084326, 11094144, 11102448, 11102465, 11102485, 11116880, 11126107, 11126919, 11127051, 11129966, 11163126, 11175208, 11175250, 11175307, 11175332, 11175459, 11178801, 11184570, 11193519, 11194902, 11197556, 11205742, 11240677, 11240737, 11275343, 11290723, 11292175, 11297364, 11297377, 11309623, 11309934, 11337174, 11337664, 11337680, 11337692) diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs index f3635af9..eb7974fe 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -36,6 +37,47 @@ public void AreaPath_is_equal() SoapResult.WorkItem[CoreFieldRefNames.AreaPath].ShouldEqual(SoapResult.WorkItem.AreaPath); } + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void Properties_of_IWorkItem_contain_similar_information() + { + var exceptions = new List(); + + Action AssertAreEqual = (restValue, soapValue) => + { + try + { + restValue.ShouldEqual(soapValue, Qwiq.GenericComparer.Default); + } + catch (Exception e) + { + exceptions.Add(e); + } + }; + + + + + try + { + AssertAreEqual(RestResult.WorkItem.Id, SoapResult.WorkItem.Id); + AssertAreEqual(RestResult.WorkItem.Title, SoapResult.WorkItem.Title); + AssertAreEqual(RestResult.WorkItem.WorkItemType, SoapResult.WorkItem.WorkItemType); + } + catch (Exception e) + { + exceptions.Add(e); + } + + + if (exceptions.Any()) + { + throw new AggregateException(exceptions.EachToUsefulString(), exceptions); + } + } + [TestMethod] [TestCategory("localOnly")] [TestCategory("SOAP")] @@ -168,6 +210,9 @@ public void CreatedDate_is_equal() [TestCategory("REST")] public void RelatedLinkCount_is_equal() { + if (((Client.Rest.WorkItemStoreConfiguration)RestResult.WorkItemStore.Configuration).WorkItemExpand != WorkItemExpand.Links) + Assert.Inconclusive("The links could not tested because the expand configuration was not set."); + RestResult.WorkItem.RelatedLinkCount.ShouldEqual(SoapResult.WorkItem.RelatedLinkCount); } } diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs index 431cdf97..cf08dbeb 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs @@ -1,5 +1,6 @@ using System; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -9,13 +10,22 @@ namespace Microsoft.Qwiq.WorkItemStore.WorkItem [TestClass] public class Given_a_WorkItem_with_Links : WorkItemWithLinksContextSpecification { + /// + public override void Given() + { + base.Given(); + ((Client.Rest.WorkItemStoreConfiguration)Rest.Configuration).WorkItemExpand = WorkItemExpand.Relations; + } + [TestMethod] [TestCategory("localOnly")] [TestCategory("REST")] [TestCategory("SOAP")] - public void links_from_both_implementations_are_equal() + [ExpectedException(typeof(NotSupportedException))] + public void AttachedFileCount_is_equal() { - RestResult.WorkItem.Links.ShouldContainOnly(SoapResult.WorkItem.Links); + AssertWorkItemExpandConfiguration(); + RestResult.WorkItem.AttachedFileCount.ShouldEqual(SoapResult.WorkItem.AttachedFileCount); } [TestMethod] @@ -24,6 +34,7 @@ public void links_from_both_implementations_are_equal() [TestCategory("SOAP")] public void ExternalLinkCount_is_equal() { + AssertWorkItemExpandConfiguration(); RestResult.WorkItem.ExternalLinkCount.ShouldEqual(SoapResult.WorkItem.ExternalLinkCount); } @@ -33,6 +44,7 @@ public void ExternalLinkCount_is_equal() [TestCategory("SOAP")] public void HyperlinkCount_is_equal() { + AssertWorkItemExpandConfiguration(); RestResult.WorkItem.HyperlinkCount.ShouldEqual(SoapResult.WorkItem.HyperlinkCount); } @@ -40,10 +52,11 @@ public void HyperlinkCount_is_equal() [TestCategory("localOnly")] [TestCategory("REST")] [TestCategory("SOAP")] - [ExpectedException(typeof(NotSupportedException))] - public void AttachedFileCount_is_equal() + public void links_from_both_implementations_are_equal() { - RestResult.WorkItem.AttachedFileCount.ShouldEqual(SoapResult.WorkItem.AttachedFileCount); + AssertWorkItemExpandConfiguration(); + + RestResult.WorkItem.Links.ShouldContainOnly(SoapResult.WorkItem.Links); } [TestMethod] @@ -52,7 +65,14 @@ public void AttachedFileCount_is_equal() [TestCategory("SOAP")] public void RelatedLinkCount_is_equal() { + AssertWorkItemExpandConfiguration(); RestResult.WorkItem.RelatedLinkCount.ShouldEqual(SoapResult.WorkItem.RelatedLinkCount); } + + private void AssertWorkItemExpandConfiguration() + { + if (((Client.Rest.WorkItemStoreConfiguration)RestResult.WorkItemStore.Configuration).WorkItemExpand != WorkItemExpand.Links) + Assert.Inconclusive("The links could not tested because the expand configuration was not set."); + } } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/MultipleIdTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/MultipleIdTests.cs index 252e2904..6f516a71 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/MultipleIdTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/MultipleIdTests.cs @@ -17,6 +17,18 @@ public override void When() } } + [TestClass] + public class Given_WorkItems_from_each_client_EagerLoad_by_IDs : Given_WorkItems_from_each_client_by_IDs + { + /// + public override void Given() + { + base.Given(); + Rest.Configuration.LazyLoadingEnabled = false; + Soap.Configuration.LazyLoadingEnabled = false; + } + } + [TestClass] public class Given_WorkItems_from_each_client_by_IDs_at_AsOf : SingleWorkItemComparisonContextSpecification { diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs index de400240..522bc1c6 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreComparisonContextSpecification.cs @@ -1,10 +1,6 @@ -using System; - -using Microsoft.Qwiq.Tests.Common; +using Microsoft.Qwiq.Tests.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Should; - namespace Microsoft.Qwiq.WorkItemStore { [DeploymentItem("Microsoft.WITDataStore32.dll")] @@ -12,11 +8,11 @@ namespace Microsoft.Qwiq.WorkItemStore [DeploymentItem("Microsoft.TeamFoundation.WorkItemTracking.Client.dll")] public abstract class WorkItemStoreComparisonContextSpecification : TimedContextSpecification { - protected IWorkItemStore Rest => RestResult.WorkItemStore; + protected internal IWorkItemStore Rest => RestResult.WorkItemStore; protected Result RestResult { get; private set; } - protected IWorkItemStore Soap => SoapResult.WorkItemStore; + protected internal IWorkItemStore Soap => SoapResult.WorkItemStore; protected Result SoapResult { get; private set; } @@ -35,17 +31,5 @@ public override void Given() RestResult = new Result { WorkItemStore = TimedAction(() => IntegrationSettings.CreateRestStore(), "REST", "Create WIS") }; } - - [TestMethod] - [TestCategory("REST")] - [TestCategory("SOAP")] - [TestCategory("localOnly")] - public void REST_implementation_is_faster_than_SOAP() - { - Durations.TryGetValue("SOAP", out TimeSpan soapTime); - Durations.TryGetValue("REST", out TimeSpan restTime); - - restTime.ShouldBeLessThan(soapTime); - } } } \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs index e364d0b6..086329c3 100644 --- a/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs +++ b/test/Qwiq.Mapper.Tests/Mocks/InstrumentedMockWorkItemStore.cs @@ -18,6 +18,9 @@ public InstrumentedMockWorkItemStore(IWorkItemStore innerWorkItemStore) public ITeamFoundationIdentity AuthorizedIdentity => _innerWorkItemStore?.AuthorizedIdentity; + /// + public WorkItemStoreConfiguration Configuration => _innerWorkItemStore.Configuration; + public IFieldDefinitionCollection FieldDefinitions => _innerWorkItemStore.FieldDefinitions; public IProjectCollection Projects diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 5cb1c8ad..f368494c 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -52,6 +52,8 @@ Func queryFactory _lookup = new Dictionary(); LinkInfo = new List(); _storeDefinitions = new Lazy(() => new MockFieldDefinitionCollection(this)); + + Configuration = new WorkItemStoreConfiguration(); } public bool SimulateQueryTimes { get; set; } @@ -112,6 +114,9 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision return query.RunLinkQuery().ToList().AsReadOnly(); } + /// + public WorkItemStoreConfiguration Configuration { get; } + public IRegisteredLinkTypeCollection RegisteredLinkTypes { get; } From ac6550777c8bc6ca8aa9e7c9875cbd9805027ac3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 09:48:15 -0700 Subject: [PATCH 207/251] Allow configurable limit for EachToUsefulString --- test/Qwiq.Tests.Common/Extensions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Qwiq.Tests.Common/Extensions.cs b/test/Qwiq.Tests.Common/Extensions.cs index af927921..139cae07 100644 --- a/test/Qwiq.Tests.Common/Extensions.cs +++ b/test/Qwiq.Tests.Common/Extensions.cs @@ -8,16 +8,16 @@ namespace Microsoft.Qwiq { public static class Extensions { - public static string EachToUsefulString(this IEnumerable enumerable) + public static string EachToUsefulString(this IEnumerable enumerable, int limit = 10) { var sb = new StringBuilder(); sb.AppendLine("{"); - sb.Append(string.Join(",\n", enumerable.Select(x => ToUsefulString(x).Tab()).Take(20).ToArray())); - if (enumerable.Count() > 20) + sb.Append(string.Join(",\n", enumerable.Select(x => ToUsefulString(x).Tab()).Take(limit).ToArray())); + if (enumerable.Count() > limit) { - if (enumerable.Count() > 21) + if (enumerable.Count() > (limit+1)) { - sb.AppendLine($",\n ...({enumerable.Count() - 20} more elements)"); + sb.AppendLine($",\n ...({enumerable.Count() - limit} more elements)"); } else { From 1caed99c1fe0a58d3244bb2f688d207d790831e3 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 10:58:52 -0700 Subject: [PATCH 208/251] Add code annotations --- src/Qwiq.Core/IWorkItemLinkInfo.cs | 3 +++ src/Qwiq.Core/WorkItemCommon.cs | 4 +++- src/Qwiq.Core/WorkItemCore.cs | 11 +++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Qwiq.Core/IWorkItemLinkInfo.cs b/src/Qwiq.Core/IWorkItemLinkInfo.cs index ef6cb72e..b8a7e793 100644 --- a/src/Qwiq.Core/IWorkItemLinkInfo.cs +++ b/src/Qwiq.Core/IWorkItemLinkInfo.cs @@ -1,9 +1,12 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public interface IWorkItemLinkInfo : IEquatable { + [CanBeNull] IWorkItemLinkTypeEnd LinkType { get; } int SourceId { get; } diff --git a/src/Qwiq.Core/WorkItemCommon.cs b/src/Qwiq.Core/WorkItemCommon.cs index e4ac8876..f8d81cf6 100644 --- a/src/Qwiq.Core/WorkItemCommon.cs +++ b/src/Qwiq.Core/WorkItemCommon.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public abstract class WorkItemCommon : WorkItemCore, IWorkItemCommon, IEquatable @@ -9,7 +11,7 @@ protected internal WorkItemCommon() { } - protected internal WorkItemCommon(Dictionary fields) + protected internal WorkItemCommon([CanBeNull] Dictionary fields) : base(fields) { } diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index 0fbbf25e..d0c148a6 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -8,6 +8,7 @@ namespace Microsoft.Qwiq { public abstract class WorkItemCore : IWorkItemCore, IEquatable, IRevisionInternal { + [CanBeNull] private readonly Dictionary _fields; protected internal WorkItemCore() @@ -51,7 +52,9 @@ public virtual object this[string name] } } - protected virtual T GetValue(string name) + [JetBrains.Annotations.Pure] + [CanBeNull] + protected virtual T GetValue([NotNull] string name) { var value = GetValue(name); @@ -71,12 +74,13 @@ protected virtual T GetValue(string name) protected virtual object GetValue([NotNull] string name) { Contract.Requires(!string.IsNullOrEmpty(name)); - + if (_fields == null) throw new InvalidOperationException("Type must be initialized with fields."); return _fields.TryGetValue(name, out object val) ? val : null; } - protected virtual void SetValue(string name, object value) + protected virtual void SetValue([NotNull] string name, [CanBeNull] object value) { + if (_fields == null) throw new InvalidOperationException("Type must be initialized with fields."); _fields[name] = value; } @@ -95,7 +99,6 @@ public override int GetHashCode() return NullableIdentifiableComparer.Default.GetHashCode(this); } - [CanBeNull] public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) { if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); From 9c27be4f1b0e104abc8623d6ca3cce88cb3a6938 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 11:20:22 -0700 Subject: [PATCH 209/251] Update Soap Query Factory behavior for INT Behavior same as REST: Use the fields defined in configuration options; otherwise, use the bare minimum. --- src/Qwiq.Core.Soap/QueryFactory.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Qwiq.Core.Soap/QueryFactory.cs b/src/Qwiq.Core.Soap/QueryFactory.cs index 0ec4c88a..2d8efd88 100644 --- a/src/Qwiq.Core.Soap/QueryFactory.cs +++ b/src/Qwiq.Core.Soap/QueryFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Globalization; using System.Linq; using JetBrains.Annotations; @@ -52,9 +53,16 @@ public IQuery Create(IEnumerable ids, DateTime? asOf = null) { // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). // It is used for ASOF - var wiql = - $"SELECT {string.Join(", ", CoreFieldRefNames.All.Select(WiqlHelpers.GetEnclosedField))} FROM {WiqlConstants.WorkItemTable}"; - if (asOf.HasValue) wiql += $" {QueryPackageNames.QueryAsOf} \'{asOf.Value:u}\'"; + FormattableString ws = $"SELECT {string.Join(", ", (_store.Configuration.DefaultFields ?? new[] { CoreFieldRefNames.Id }).Select(WiqlHelpers.GetEnclosedField))} FROM {WiqlConstants.WorkItemTable}"; + var wiql = ws.ToString(CultureInfo.InvariantCulture); + + if (asOf.HasValue) + { + // If specified DateTime is not UTC convert it to local time based on TFS client TimeZone + if (asOf.Value.Kind != DateTimeKind.Utc) asOf = _store.NativeWorkItemStore.TimeZone.ToUniversalTime(asOf.Value); + FormattableString ao = $" ASOF \'{asOf.Value:u}\'"; + wiql += ao.ToString(CultureInfo.InvariantCulture); + } return Create(ids, wiql); } } From ec5cc1721db6d31521f4d9f924197897e30626f8 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 11:26:31 -0700 Subject: [PATCH 210/251] Correct NullReferenceException issues with links When querying for a link, a value may be returned with a source of null, target of an item, and a link of null. In SOAP: 0, 12345, 0 respectively In Rest: NULL, 12345, NULL respectively This represents a "self" link, indicating the value is a root (e.g. has no parents) in a recursive or hierarchy query. However, when attempting to evaluate the link type lazily either by calling the `LinkType` property or `ToString` method, a NullReferenceException was thrown because the value of the property is also null. This was not noticed before because items were never under test or the `LinkType` property was never called. Since the value was always evaluated lazily, items ran through the system without issue. Since this is a valid link and is used to indicate a "root" when composing a hierarchy, but is not a valid link type, the logic has been updated to permit error-free execution in eager load scenarios. Tests have been added to support the various hierarchy scenarios with various runtime flags to execute both eager and lazy code paths. --- src/Qwiq.Core.Rest/Query.cs | 124 ++++++----- .../WorkItemStoreConfiguration.cs | 61 +++--- src/Qwiq.Core.Soap/Query.cs | 11 +- src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj | 1 + src/Qwiq.Core.Soap/WorkItemStore.cs | 50 +++-- .../WorkItemStoreConfiguration.cs | 25 +++ src/Qwiq.Core/Qwiq.Core.csproj | 2 + src/Qwiq.Core/WorkItemErrorPolicy.cs | 8 + src/Qwiq.Core/WorkItemExpand.cs | 11 + src/Qwiq.Core/WorkItemLinkInfo.cs | 48 ++--- src/Qwiq.Core/WorkItemStoreConfiguration.cs | 31 ++- .../Qwiq.IntegrationTests.csproj | 1 + .../LargeHierarchyContextSpecification.cs | 39 ++++ .../LargeWiqlHierarchyQueryTests.cs | 93 +++------ ...rationContextSpecificationSpecification.cs | 194 ++++++++++-------- .../WorkItemStore/WorkItem/LinkTests.cs | 8 +- test/Qwiq.Mocks/MockWorkItemStore.cs | 148 +++++-------- .../MockWorkItemStoreConfiguration.cs | 19 ++ test/Qwiq.Mocks/Qwiq.Mocks.csproj | 1 + 19 files changed, 453 insertions(+), 422 deletions(-) create mode 100644 src/Qwiq.Core.Soap/WorkItemStoreConfiguration.cs create mode 100644 src/Qwiq.Core/WorkItemErrorPolicy.cs create mode 100644 src/Qwiq.Core/WorkItemExpand.cs create mode 100644 test/Qwiq.Integration.Tests/WorkItemStore/LargeHierarchyContextSpecification.cs create mode 100644 test/Qwiq.Mocks/MockWorkItemStoreConfiguration.cs diff --git a/src/Qwiq.Core.Rest/Query.cs b/src/Qwiq.Core.Rest/Query.cs index 6ba915da..fe0fc7cd 100644 --- a/src/Qwiq.Core.Rest/Query.cs +++ b/src/Qwiq.Core.Rest/Query.cs @@ -1,3 +1,5 @@ +using JetBrains.Annotations; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -7,10 +9,6 @@ using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; - -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - namespace Microsoft.Qwiq.Client.Rest { internal class Query : IQuery @@ -68,6 +66,7 @@ public IWorkItemLinkTypeEndCollection GetLinkTypes() [ItemNotNull] public IEnumerable RunLinkQuery() { + // REVIEW: Create an IWorkItemLinkInfo like IWorkItemLinkTypeEndCollection and IWorkItemCollection return _workItemStore.Configuration.LazyLoadingEnabled ? RunkLinkQueryImplLazy() : RunkLinkQueryImpl(); } @@ -111,11 +110,56 @@ IWorkItemType WorkItemTypeFactory() return new WorkItem(workItem, new Lazy(WorkItemTypeFactory), LinkFunc); } + private List[] FetchResults() + { + var t = new CancellationToken(); + var ts = + new List>>( + _ids.Count + % _workItemStore.Configuration.PageSize + + 1); + var qry = _ids.Partition(_workItemStore.Configuration.PageSize); + + var c = _workItemStore.NativeWorkItemStore.Value; + var o = _workItemStore.Configuration; + + var e = (TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemExpand)o.WorkItemExpand; + var p = (TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemErrorPolicy)o.WorkItemErrorPolicy; + var f = o.WorkItemExpand == WorkItemExpand.None ? o.DefaultFields : null; + + foreach (var s in qry) ts.Add(c.GetWorkItemsAsync(s, f, _asOf, e, p, null, t)); + // This is done in parallel so keep performance similar to the SOAP client + var results = Task.WhenAll(ts.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); + return results; + } + private IWorkItemLinkType LinkFunc(string s) { return _workItemStore.WorkItemLinkTypes[s]; } + private List LoadWorkItemsEagerly(List[] results) + { + var retval = new List(_ids.Count); + + // This is done in parallel so keep performance similar to the SOAP client + for (var i = 0; i < results.Length; i++) + { + var workItemsPartition = results[i]; + // REIVEW: Allocate for workItem variable + for (var j = 0; j < workItemsPartition.Count; j++) + { + var workItem = workItemsPartition[j]; + + var wi = CreateItemEager(workItem); + + retval.Add(_workItemStore.Configuration.ProxyCreationEnabled ? wi.AsProxy() : wi); + } + } + + return retval; + } + private ReadOnlyCollection RunkLinkQueryImpl() { // Eager loading for the link type ID (which is not returned by the REST API) causes ~250ms delay @@ -124,15 +168,11 @@ private ReadOnlyCollection RunkLinkQueryImpl() // To avoid an enumerator allocation we are forcing the cast var result2 = (List)result.WorkItemRelations; var retval = new List(result2.Count + 1); - // REVIEW: Closure variable "ends" allocates, preventing local cache var ends = GetLinkTypes(); for (var index = 0; index < result2.Count; index++) { ends.TryGetByName(result2[index].Rel, out IWorkItemLinkTypeEnd end); - - if (end == null) continue; - retval.Add(new WorkItemLinkInfo(result2[index].Source?.Id ?? 0, result2[index].Target?.Id ?? 0, end)); } @@ -151,24 +191,26 @@ private IEnumerable RunkLinkQueryImplLazy() // To avoid an enumerator allocation we are forcing the cast var result2 = (List)result.WorkItemRelations; - for (var index = 0; index < result2.Count; index++) + for (var i = 0; i < result2.Count; i++) { + WorkItemLink t = result2[i]; + // REVIEW: Closure allocation: workItemLink + ends outer closure IWorkItemLinkTypeEnd EndValueFactory() { - return ends.Value.TryGetByName(result2[index].Rel, out IWorkItemLinkTypeEnd end) ? end : null; + return ends.Value.TryGetByName(t.Rel, out IWorkItemLinkTypeEnd end) ? end : null; } var ltEnd = new Lazy(EndValueFactory); - yield return new WorkItemLinkInfo(result2[index].Source?.Id ?? 0, result2[index].Target?.Id ?? 0, ltEnd); + yield return new WorkItemLinkInfo(t.Source?.Id ?? 0, t.Target?.Id ?? 0, ltEnd); } } [NotNull] private List RunQueryImpl() { - Contract.Ensures(Contract.Result>() != null); + Contract.Ensures(Contract.Result>() != null); if (_ids == null && _query != null) { @@ -185,41 +227,9 @@ private List RunQueryImpl() if (_ids == null) return EmptyWorkItems; - var retval = new List(_ids.Count); - var t = new CancellationToken(); - var ts = - new List>>( - _ids.Count - % _workItemStore.Configuration.PageSize - + 1); - var qry = _ids.Partition(_workItemStore.Configuration.PageSize); - - var c = _workItemStore.NativeWorkItemStore.Value; - var o = _workItemStore.Configuration; - - foreach (var s in qry) - { - ts.Add(c.GetWorkItemsAsync(s, null, _asOf, o.WorkItemExpand, o.WorkItemErrorPolicy, null, t)); - } - - var results = Task.WhenAll(ts.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); - - // This is done in parallel so keep performance similar to the SOAP client - for (var i = 0; i < results.Length; i++) - { - var workItemsPartition = results[i]; - // REIVEW: Allocate for workItem variable - for (var j = 0; j < workItemsPartition.Count; j++) - { - var workItem = workItemsPartition[j]; + var results = FetchResults(); - var wi = _workItemStore.Configuration.LazyLoadingEnabled ? CreateItemLazy(workItem) : CreateItemEager(workItem); - - retval.Add(_workItemStore.Configuration.ProxyCreationEnabled ? wi.AsProxy() : wi); - } - } - - return retval; + return LoadWorkItemsEagerly(results); } [NotNull] @@ -242,29 +252,13 @@ private IEnumerable RunQueryImplLazy() if (_ids == null) yield break; - var t = new CancellationToken(); - var ts = - new List>>( - _ids.Count - % _workItemStore.Configuration.PageSize - + 1); - var qry = _ids.Partition(_workItemStore.Configuration.PageSize); - var o = _workItemStore.Configuration; - var c = _workItemStore.NativeWorkItemStore.Value; - var f = o.WorkItemExpand == WorkItemExpand.Fields || o.WorkItemExpand == WorkItemExpand.All ? null : o.DefaultFields; - - foreach (var s in qry) - { - ts.Add(c.GetWorkItemsAsync(s, f, _asOf, o.WorkItemExpand, o.WorkItemErrorPolicy, null, t)); - } - - var results = Task.WhenAll(ts.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); + var results = FetchResults(); - // This is done in parallel so keep performance similar to the SOAP client + // This is not encapsulated in own method to avoid allocation of iterator for (var i = 0; i < results.Length; i++) { var workItemsPartition = results[i]; - // REIVEW: Allocate for workItem variable + for (var j = 0; j < workItemsPartition.Count; j++) { var workItem = workItemsPartition[j]; diff --git a/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs b/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs index 580fb8ab..2def4d35 100644 --- a/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs +++ b/src/Qwiq.Core.Rest/WorkItemStoreConfiguration.cs @@ -1,50 +1,48 @@ using System.Collections.Generic; using System.Diagnostics; -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; - namespace Microsoft.Qwiq.Client.Rest { - public class WorkItemStoreConfiguration : Qwiq.WorkItemStoreConfiguration + public sealed class WorkItemStoreConfiguration : Qwiq.WorkItemStoreConfiguration { - private WorkItemExpand _workItemExpand; + private static readonly string[] DefaultFieldValue = + { + CoreFieldRefNames.AssignedTo, + CoreFieldRefNames.ChangedBy, + CoreFieldRefNames.ChangedDate, + CoreFieldRefNames.Id, + CoreFieldRefNames.State, + CoreFieldRefNames.TeamProject, + CoreFieldRefNames.Title, + CoreFieldRefNames.WorkItemType + }; private IEnumerable _defaultFields; - private static readonly string[] DefaultFieldValue = { - CoreFieldRefNames.AssignedTo, - CoreFieldRefNames.ChangedBy, - CoreFieldRefNames.ChangedDate, - CoreFieldRefNames.Id, - CoreFieldRefNames.State, - CoreFieldRefNames.TeamProject, - CoreFieldRefNames.Title, - CoreFieldRefNames.WorkItemType - }; + private WorkItemExpand _workItemExpand; internal WorkItemStoreConfiguration() { WorkItemExpand = WorkItemExpand.All; WorkItemErrorPolicy = WorkItemErrorPolicy.Omit; - DefaultFields = null; } /// - /// Gets or sets a value indicating which fields to load when querying for a work item. Has no effect if - /// is set to or - /// . Defaults to - /// , , - /// , , - /// , + /// Gets or sets a value indicating which fields to load when querying for a work item. Has no effect if is set to or . Defaults to , + /// , , , , /// - public IEnumerable DefaultFields + public sealed override IEnumerable DefaultFields { - get { return _defaultFields; } + get => _defaultFields; set { + if (Equals(_defaultFields, value)) return; + if (WorkItemExpand != WorkItemExpand.None) { - Trace.TraceWarning($"The {nameof(WorkItemExpand)} parameter can not be used with the {nameof(DefaultFields)} parameter."); + Trace.TraceWarning( + $"The {nameof(DefaultFields)} parameter can not be used with the {nameof(WorkItemExpand)} parameter. Setting {nameof(WorkItemExpand)} to {WorkItemExpand.None}."); _workItemExpand = WorkItemExpand.None; } _defaultFields = value; @@ -52,26 +50,23 @@ public IEnumerable DefaultFields } /// - /// Gets or sets a value indicating the expected behavior when querying for a work item that does not exist. Default - /// value is + /// Gets or sets a value indicating the expected behavior when querying for a work item that does not exist. Default value is /// - public WorkItemErrorPolicy WorkItemErrorPolicy { get; set; } + public override WorkItemErrorPolicy WorkItemErrorPolicy { get; set; } /// - /// Gets or sets a value indicating which sub-elements of - /// to expand. Default value is - /// . + /// Gets or sets a value indicating which sub-elements of to expand. Default value is . /// - public WorkItemExpand WorkItemExpand + public override WorkItemExpand WorkItemExpand { - get { return _workItemExpand; } + get => _workItemExpand; set { _workItemExpand = value; if (value != WorkItemExpand.None) { Trace.TraceWarning( - $"The {nameof(WorkItemExpand)} parameter can not be used with the {nameof(DefaultFields)} parameter."); + $"The {nameof(WorkItemExpand)} parameter can not be used with the {nameof(DefaultFields)} parameter. Setting {nameof(DefaultFields)} to NULL."); _defaultFields = null; } else diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index 1921749b..38cda575 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -13,12 +13,14 @@ internal class Query : IQuery { private readonly int _pageSize; + [NotNull] private readonly TeamFoundation.WorkItemTracking.Client.Query _query; internal Query([NotNull] TeamFoundation.WorkItemTracking.Client.Query query, int pageSize) { Contract.Requires(query != null); - _query = query; + + _query = query ?? throw new ArgumentNullException(nameof(query)); _pageSize = pageSize; } @@ -51,12 +53,7 @@ public IEnumerable RunLinkQuery() var lt = GetLinkTypes().ToDictionary(k=>k.Id, e=>e); for (var i = 0; i < wili.Length; i++) { - if (wili[i].LinkTypeId == 0) continue; - - // TODO: Use Lazy config options - - - var lte = lt[wili[i].LinkTypeId]; + lt.TryGetValue(wili[i].LinkTypeId, out IWorkItemLinkTypeEnd lte) ; retval.Add(new WorkItemLinkInfo(wili[i].SourceId, wili[i].TargetId, lte)); } diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index b015eed8..25ea5454 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -211,6 +211,7 @@ + diff --git a/src/Qwiq.Core.Soap/WorkItemStore.cs b/src/Qwiq.Core.Soap/WorkItemStore.cs index cdf16917..5a9612c5 100644 --- a/src/Qwiq.Core.Soap/WorkItemStore.cs +++ b/src/Qwiq.Core.Soap/WorkItemStore.cs @@ -1,15 +1,16 @@ -using Microsoft.Qwiq.Exceptions; -using Microsoft.VisualStudio.Services.Common; using System; using System.Collections.Generic; using System.Linq; + +using Microsoft.Qwiq.Exceptions; +using Microsoft.VisualStudio.Services.Common; + using TfsWorkItem = Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Microsoft.Qwiq.Client.Soap { /// - /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference - /// all the TFS libraries. + /// Wrapper around the TFS WorkItemStore. This exists so that every agent doesn't need to reference all the TFS libraries. /// internal class WorkItemStore : IWorkItemStore { @@ -40,7 +41,10 @@ internal WorkItemStore( IWorkItemLinkTypeCollection WorkItemLinkTypeCollectionFactory() { - return new WorkItemLinkTypeCollection(_workItemStore.Value.WorkItemLinkTypes.Select(item => (IWorkItemLinkType)new WorkItemLinkType(item)).ToList()); + return new WorkItemLinkTypeCollection( + _workItemStore + .Value.WorkItemLinkTypes + .Select(item => (IWorkItemLinkType)new WorkItemLinkType(item)).ToList()); } _workItemLinkTypes = new Lazy(WorkItemLinkTypeCollectionFactory); @@ -59,42 +63,37 @@ IRegisteredLinkTypeCollection RegisteredLinkTypeCollectionFactory() _projects = new Lazy(() => new ProjectCollection(_workItemStore.Value.Projects)); Configuration = new WorkItemStoreConfiguration(); - - } - internal WorkItemStore( - Func tpcFactory, - Func queryFactory) + internal WorkItemStore(Func tpcFactory, Func queryFactory) : this(tpcFactory, () => tpcFactory?.Invoke()?.GetService(), queryFactory) { } + + + /// + /// + public WorkItemStoreConfiguration Configuration { get; } + + public IProjectCollection Projects => _projects.Value; + public IRegisteredLinkTypeCollection RegisteredLinkTypes => _linkTypes.Value; + public ITeamProjectCollection TeamProjectCollection => _tfs.Value; + public IWorkItemLinkTypeCollection WorkItemLinkTypes => _workItemLinkTypes.Value; public VssCredentials AuthorizedCredentials => _tfs.Value.AuthorizedCredentials; public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection?.AuthorizedIdentity; - + Qwiq.WorkItemStoreConfiguration IWorkItemStore.Configuration => Configuration; + internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; public IFieldDefinitionCollection FieldDefinitions => ExceptionHandlingDynamicProxyFactory.Create( - new + new FieldDefinitionCollection( _workItemStore .Value .FieldDefinitions)); - - - public IProjectCollection Projects => _projects.Value; - - public IRegisteredLinkTypeCollection RegisteredLinkTypes => _linkTypes.Value; - - public ITeamProjectCollection TeamProjectCollection => _tfs.Value; - public TimeZone TimeZone => _workItemStore.Value.TimeZone; - public IWorkItemLinkTypeCollection WorkItemLinkTypes => _workItemLinkTypes.Value; - - internal TfsWorkItem.WorkItemStore NativeWorkItemStore => _workItemStore.Value; - public void Dispose() { Dispose(true); @@ -149,9 +148,6 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision } } - /// - public WorkItemStoreConfiguration Configuration { get; } - protected virtual void Dispose(bool disposing) { if (disposing) if (_tfs.IsValueCreated) _tfs.Value?.Dispose(); diff --git a/src/Qwiq.Core.Soap/WorkItemStoreConfiguration.cs b/src/Qwiq.Core.Soap/WorkItemStoreConfiguration.cs new file mode 100644 index 00000000..3cd573ec --- /dev/null +++ b/src/Qwiq.Core.Soap/WorkItemStoreConfiguration.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Microsoft.Qwiq.Client.Soap +{ + public class WorkItemStoreConfiguration : Qwiq.WorkItemStoreConfiguration + { + internal WorkItemStoreConfiguration() + { + WorkItemExpand = WorkItemExpand.All; + DefaultFields = CoreFieldRefNames.All; + } + + /// + /// + public override IEnumerable DefaultFields { get; set; } + + /// + /// + public override WorkItemErrorPolicy WorkItemErrorPolicy { get; set; } + + /// + /// + public override WorkItemExpand WorkItemExpand { get; set; } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 2325a9e7..bc08bdef 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -199,6 +199,8 @@ + + diff --git a/src/Qwiq.Core/WorkItemErrorPolicy.cs b/src/Qwiq.Core/WorkItemErrorPolicy.cs new file mode 100644 index 00000000..dc2c018b --- /dev/null +++ b/src/Qwiq.Core/WorkItemErrorPolicy.cs @@ -0,0 +1,8 @@ +namespace Microsoft.Qwiq +{ + public enum WorkItemErrorPolicy + { + Fail = 1, + Omit = 2 + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemExpand.cs b/src/Qwiq.Core/WorkItemExpand.cs new file mode 100644 index 00000000..b59972aa --- /dev/null +++ b/src/Qwiq.Core/WorkItemExpand.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Qwiq +{ + public enum WorkItemExpand + { + None, + Relations, + Fields, + Links, + All + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkInfo.cs b/src/Qwiq.Core/WorkItemLinkInfo.cs index 2f0f400d..c9a9cbef 100644 --- a/src/Qwiq.Core/WorkItemLinkInfo.cs +++ b/src/Qwiq.Core/WorkItemLinkInfo.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Diagnostics.Contracts; using System.Globalization; using JetBrains.Annotations; @@ -10,27 +9,40 @@ namespace Microsoft.Qwiq public class WorkItemLinkInfo : IWorkItemLinkInfo { [CanBeNull] - private readonly IWorkItemLinkTypeEnd _linkTypeEnd; + private Lazy _lazyLinkTypeEnd; [CanBeNull] - private readonly Lazy _lazyLinkTypeEnd; + private IWorkItemLinkTypeEnd _linkTypeEnd; - internal WorkItemLinkInfo(int sourceId, int targetId, [NotNull] IWorkItemLinkTypeEnd linkTypeEnd) - : this(sourceId, targetId, (Lazy) null) + internal WorkItemLinkInfo(int sourceId, int targetId, [CanBeNull] IWorkItemLinkTypeEnd linkTypeEnd) { - Contract.Requires(linkTypeEnd != null); - - _linkTypeEnd = linkTypeEnd ?? throw new ArgumentNullException(nameof(linkTypeEnd)); + SourceId = sourceId; + TargetId = targetId; + _linkTypeEnd = linkTypeEnd; } - internal WorkItemLinkInfo(int sourceId, int targetId, [CanBeNull] Lazy linkTypeEnd) + internal WorkItemLinkInfo(int sourceId, int targetId, [NotNull] Lazy linkTypeEnd) { SourceId = sourceId; TargetId = targetId; - _lazyLinkTypeEnd = linkTypeEnd; + _lazyLinkTypeEnd = linkTypeEnd ?? throw new ArgumentNullException(nameof(linkTypeEnd)); } - public IWorkItemLinkTypeEnd LinkType => _linkTypeEnd ?? _lazyLinkTypeEnd?.Value ?? throw new InvalidOperationException(); + public IWorkItemLinkTypeEnd LinkType + { + get + { + if (_linkTypeEnd != null) return _linkTypeEnd; + if (_lazyLinkTypeEnd != null) + { + _linkTypeEnd = _lazyLinkTypeEnd.Value; + _lazyLinkTypeEnd = null; + return _linkTypeEnd; + } + + return null; + } + } public int SourceId { get; } @@ -42,18 +54,6 @@ public bool Equals(IWorkItemLinkInfo other) return WorkItemLinkInfoComparer.Default.Equals(this, other); } - [DebuggerStepThrough] - public static bool operator !=(WorkItemLinkInfo x, WorkItemLinkInfo y) - { - return !WorkItemLinkInfoComparer.Default.Equals(x, y); - } - - [DebuggerStepThrough] - public static bool operator ==(WorkItemLinkInfo x, WorkItemLinkInfo y) - { - return WorkItemLinkInfoComparer.Default.Equals(x, y); - } - [DebuggerStepThrough] public override bool Equals(object obj) { @@ -68,7 +68,7 @@ public override int GetHashCode() public override string ToString() { - return $"S:{SourceId} T:{TargetId} Type:{LinkType}".ToString(CultureInfo.InvariantCulture); + return $"S:{SourceId} T:{TargetId} Type:{LinkType?.ImmutableName ?? "UNKNOWN"}".ToString(CultureInfo.InvariantCulture); } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemStoreConfiguration.cs b/src/Qwiq.Core/WorkItemStoreConfiguration.cs index effe5c5d..a5f1d822 100644 --- a/src/Qwiq.Core/WorkItemStoreConfiguration.cs +++ b/src/Qwiq.Core/WorkItemStoreConfiguration.cs @@ -1,28 +1,26 @@ -using System.Diagnostics.Contracts; +using System.Collections.Generic; +using System.Diagnostics.Contracts; namespace Microsoft.Qwiq { - public class WorkItemStoreConfiguration + public abstract class WorkItemStoreConfiguration { private int _pageSize; - internal WorkItemStoreConfiguration() + protected WorkItemStoreConfiguration() { LazyLoadingEnabled = true; ProxyCreationEnabled = true; PageSize = 50; } + public abstract IEnumerable DefaultFields { get; set; } + /// /// Gets or sets a value indicating whether lazy loading of certain properties is enabled. Lazy loading is enabled by default /// public bool LazyLoadingEnabled { get; set; } - /// - /// Gets or sets a value indicating whether or not the framework will create instances of dynamically generated proxy classes whenever it creates an instance of a type. Note that even if proxy creation is enabled with this flag, proxy instances will only be created for entity types that meet the requirements for being proxied. Proxy creation is enabled by default. - /// - public bool ProxyCreationEnabled { get;set;} - /// /// Gets or sets a value indicating the number of work items to be returned in a page. Value is 50 by default. /// @@ -38,12 +36,23 @@ public int PageSize Contract.Requires(value >= MinimumBatchSize); Contract.Requires(value <= MaximumBatchSize); - if (value < MinimumBatchSize || value > MaximumBatchSize) throw new PageSizeRangeException(); - _pageSize = value; } } + + /// + /// Gets or sets a value indicating whether or not the framework will create instances of dynamically generated proxy classes whenever it creates an instance of a type. + /// + /// + /// Note that even if proxy creation is enabled with this flag, proxy instances will only be created for entity types that meet the requirements for being proxied. Proxy + /// creation is enabled by default. + /// + public bool ProxyCreationEnabled { get; set; } + + public abstract WorkItemErrorPolicy WorkItemErrorPolicy { get; set; } + + public abstract WorkItemExpand WorkItemExpand { get; set; } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 78570295..070a82fa 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -203,6 +203,7 @@ + diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/LargeHierarchyContextSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/LargeHierarchyContextSpecification.cs new file mode 100644 index 00000000..2513a1c0 --- /dev/null +++ b/test/Qwiq.Integration.Tests/WorkItemStore/LargeHierarchyContextSpecification.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq.WorkItemStore +{ + public abstract class LargeHierarchyContextSpecification : WorkItemStoreComparisonContextSpecification + { + internal const string WIQL = @" +SELECT [System.Id], [System.Title] +FROM WorkItemLinks +WHERE + Source.[System.Id] IN (10726623) + AND [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' +mode(Recursive) +"; + + public override void When() + { + RestResult.Links = TimedAction(() => RestResult.WorkItemStore.QueryLinks(WIQL).ToList(), "REST", "QueryLinks - WIQL"); + var hs = new HashSet(RestResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId })); + hs.Remove(0); + RestResult.WorkItems = TimedAction( + () => RestResult.WorkItemStore.Query(hs), + "REST", + "Query - IDs"); + + + + SoapResult.Links = TimedAction(() => SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(), "SOAP", "QueryLinks - WIQL"); + hs = new HashSet(SoapResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId })); + hs.Remove(0); + + SoapResult.WorkItems = TimedAction( + () => SoapResult.WorkItemStore.Query(hs), + "SOAP", + "Query - IDs"); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs index 7de917b7..7869d937 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/LargeWiqlHierarchyQueryTests.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -8,71 +5,8 @@ namespace Microsoft.Qwiq.WorkItemStore { [TestClass] - public class LargeWiqlHierarchyQueryTests_EagerLoad : LargeWiqlHierarchyQueryTests + public class LargeWiqlHierarchyQueryTests : LargeHierarchyContextSpecification { - /// - public override void Given() - { - base.Given(); - Soap.Configuration.LazyLoadingEnabled = false; - Rest.Configuration.LazyLoadingEnabled = false; - } - } - - [TestClass] - public class LargeWiqlHierarchyQueryTests : WorkItemStoreComparisonContextSpecification - { - internal const string WIQL = @" -SELECT [System.Id], [System.Title] -FROM WorkItemLinks -WHERE - Source.[System.Id] IN (306417, 3135398, 3249369, 3492942, 4233618, 4708876, 4774628, 4774633, 5081979, 5145876, 5321076, 5347847, 5348414, 5399017, 5399125, 5399142, 5437042, 5580845, 5696165, 5755004, 5779431, 5791052, 5810594, 5814045, 6190723, 6340710, 6418471, 6420861, 6507248, 6966852, 7147489, 7358985, 7418008, 7462755, 7529329, 7547051, 7547912, 7578388, 7726674, 7734222, 7734226, 7734230, 7734245, 7734252, 7734254, 7734255, 7734260, 7734261, 7734269, 7802385, 7825004, 7881302, 7899054, 7899207, 7899214, 7899226, 7925414, 7978728, 7989565, 8040116, 8062597, 8083468, 8122369, 8168698, 8169478, 8169633, 8172195, 8219936, 8233267, 8240155, 8249707, 8256930, 8256939, 8256943, 8256947, 8273089, 8273096, 8274155, 8285368, 8291862, 8305945, 8311776, 8329534, 8402025, 8415967, 8461788, 8461811, 8464765, 8478988, 8479926, 8480154, 8480258, 8521180, 8523040, 8584490, 8647508, 8704880, 8909592, 9067373, 9197486, 9197751, 9256654, 9279773, 9403332, 9405201, 9416107, 9445547, 9451633, 9492546, 9637927, 9644862, 9647371, 9780582, 9796760, 9831841, 9836943, 9853813, 9941648, 10397497, 10397521, 10398675, 10448096, 10471325, 10499131, 10513555, 10527610, 10530854, 10552224, 10562556, 10588201, 10597347, 10597392, 10621199, 10621202, 10629409, 10650244, 10693987, 10696618, 10726461, 10726528, 10726538, 10726549, 10726569, 10726584, 10726595, 10726623, 10726641, 10731011, 10732765, 10748682, 10753466, 10754027, 10757138, 10760613, 10760657, 10760716, 10760745, 10760797, 10761005, 10762808, 10764882, 10769936, 10769966, 10769985, 10770045, 10770155, 10772744, 10781600, 10781696, 10782702, 10783490, 10784489, 10785622, 10786336, 10786381, 10788814, 10788938, 10789724, 10789737, 10795841, 10796505, 10796527, 10796531, 10796651, 10797459, 10799250, 10799746, 10802354, 10802569, 10803640, 10803778, 10804396, 10804706, 10825656, 10833432, 10834702, 10835610, 10835947, 10854312, 10872890, 10872990, 10875938, 10887469, 10888050, 10889671, 10893905, 10919153, 10940803, 10940927, 10955448, 10955522, 10956000, 10956061, 10956625, 10961487, 10961734, 10961908, 10983282, 10984200, 10991608, 11013506, 11017539, 11018019, 11018123, 11018139, 11018157, 11018183, 11018222, 11018336, 11053481, 11057194, 11070906, 11076205, 11084268, 11084305, 11084326, 11094144, 11102448, 11102465, 11102485, 11116880, 11126107, 11126919, 11127051, 11129966, 11163126, 11175208, 11175250, 11175307, 11175332, 11175459, 11178801, 11184570, 11193519, 11194902, 11197556, 11205742, 11240677, 11240737, 11275343, 11290723, 11292175, 11297364, 11297377, 11309623, 11309934, 11337174, 11337664, 11337680, 11337692) - AND [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' - AND Target.[System.AreaPath] UNDER 'OS\Core\WebPlat' - AND Target.[System.WorkItemType] IN ('Customer Promise', 'Scenario', 'Measure', 'Deliverable', 'Task') -mode(Recursive) -"; - - - public override void When() - { - RestResult.Links = TimedAction( - () => RestResult.WorkItemStore.QueryLinks(WIQL).ToList(), - "REST", - "QueryLinks - WIQL"); - RestResult.WorkItems = TimedAction( - () => RestResult.WorkItemStore.Query(new HashSet(RestResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId }).Where(i => i != 0))), - "REST", - "Query - IDs"); - - SoapResult.Links = TimedAction( - () => SoapResult.WorkItemStore.QueryLinks(WIQL).ToList(), - "SOAP", - "QueryLinks - WIQL"); - SoapResult.WorkItems = TimedAction( - () => SoapResult.WorkItemStore.Query(new HashSet(SoapResult.Links.SelectMany(dl => new[] { dl.TargetId, dl.SourceId }).Where(i => i != 0))), - "SOAP", - "Query - IDs"); - } - - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - [TestCategory("REST")] - public void Link_Count_Equal() - { - RestResult.Links.Count().ShouldEqual(SoapResult.Links.Count(), "WorkItemLinks.Count"); - } - - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - [TestCategory("REST")] - public void WorkItem_Count_Equal() - { - RestResult.WorkItems.Count.ShouldEqual(SoapResult.WorkItems.Count(), "WorkItems.Count"); - } - [TestMethod] [TestCategory("localOnly")] [TestCategory("SOAP")] @@ -92,5 +26,30 @@ public void WorkItems_Equal() } } + [TestClass] + public class LargeWiqlHierarchyQueryTests_EagerLoad : LargeWiqlHierarchyQueryTests + { + /// + /// + public override void Given() + { + base.Given(); + Soap.Configuration.LazyLoadingEnabled = false; + Rest.Configuration.LazyLoadingEnabled = false; + } + } + + [TestClass] + public class LargeWiqlHierarchyQueryTests_EagerLoad_NoFieldExpansion : LargeWiqlHierarchyQueryTests_EagerLoad + { + /// + /// + public override void Given() + { + base.Given(); + Soap.Configuration.WorkItemExpand = WorkItemExpand.None; + Rest.Configuration.WorkItemExpand = WorkItemExpand.None; + } + } } \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs index eb7974fe..47e269b6 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/IntegrationContextSpecificationSpecification.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -14,6 +13,15 @@ public class Given_a_WorkItem_from_each_WorkItemStore_implementation : Integrati { private const int Id = 10726528; + /// + public override void Given() + { + base.Given(); + Soap.Configuration.DefaultFields = CoreFieldRefNames.All; + Rest.Configuration.DefaultFields = CoreFieldRefNames.All; + Rest.Configuration.WorkItemExpand = WorkItemExpand.All; + } + public override void When() { SoapResult.WorkItem = TimedAction(() => SoapResult.WorkItemStore.Query(Id), "SOAP", "Query By Id"); @@ -41,41 +49,27 @@ public void AreaPath_is_equal() [TestCategory("localOnly")] [TestCategory("SOAP")] [TestCategory("REST")] - public void Properties_of_IWorkItem_contain_similar_information() + public void Core_DateTime_fields_contain_similar_information() { var exceptions = new List(); + var dateTimeFields = new[] { CoreFieldRefNames.ChangedDate, CoreFieldRefNames.CreatedDate }; - Action AssertAreEqual = (restValue, soapValue) => - { - try - { - restValue.ShouldEqual(soapValue, Qwiq.GenericComparer.Default); - } - catch (Exception e) - { - exceptions.Add(e); - } - }; - - - - + foreach (var field in dateTimeFields) + { try { - AssertAreEqual(RestResult.WorkItem.Id, SoapResult.WorkItem.Id); - AssertAreEqual(RestResult.WorkItem.Title, SoapResult.WorkItem.Title); - AssertAreEqual(RestResult.WorkItem.WorkItemType, SoapResult.WorkItem.WorkItemType); - } + var restValue = (DateTime?)RestResult.WorkItem[field]; + var soapValue = (DateTime)SoapResult.WorkItem[field]; + + restValue.GetValueOrDefault().ShouldEqual(soapValue.ToUniversalTime(), field); + } catch (Exception e) { exceptions.Add(e); } - - - if (exceptions.Any()) - { - throw new AggregateException(exceptions.EachToUsefulString(), exceptions); } + + if (exceptions.Any()) throw new AggregateException(exceptions.EachToUsefulString(), exceptions); } [TestMethod] @@ -87,8 +81,10 @@ public void Core_identity_fields_contain_similar_information() var exceptions = new List(); var identityFields = new[] { - CoreFieldRefNames.AssignedTo, CoreFieldRefNames.AuthorizedAs, - CoreFieldRefNames.ChangedBy, CoreFieldRefNames.CreatedBy + CoreFieldRefNames.AssignedTo, + CoreFieldRefNames.AuthorizedAs, + CoreFieldRefNames.ChangedBy, + CoreFieldRefNames.CreatedBy }; foreach (var field in identityFields) @@ -110,51 +106,104 @@ public void Core_identity_fields_contain_similar_information() } } + if (exceptions.Any()) throw new AggregateException(exceptions.EachToUsefulString(), exceptions); + } - if (exceptions.Any()) + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void CoreFields_by_Fields_indexer_are_equal() + { + string GetValue(IWorkItem item, string field) { - throw new AggregateException(exceptions.EachToUsefulString(), exceptions); + return item.Fields[field]?.Value?.ToString(); } + + GetCoreFieldComparisonAssertions(GetValue); } [TestMethod] [TestCategory("localOnly")] [TestCategory("SOAP")] [TestCategory("REST")] - public void Core_DateTime_fields_contain_similar_information() + public void CoreFields_by_this_indexer_are_equal() + { + string GetValue(IWorkItem item, string field) + { + return item[field]?.ToString(); + } + + GetCoreFieldComparisonAssertions(GetValue); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void CreatedDate_is_equal() + { + RestResult.WorkItem.CreatedDate.ShouldEqual(SoapResult.WorkItem.CreatedDate.ToUniversalTime()); + } + + [TestMethod] + [TestCategory("localOnly")] + [TestCategory("SOAP")] + [TestCategory("REST")] + public void Properties_of_IWorkItem_contain_similar_information() { var exceptions = new List(); - var dateTimeFields = new[] { CoreFieldRefNames.ChangedDate, CoreFieldRefNames.CreatedDate, }; - foreach (var field in dateTimeFields) - { - try - { - var restValue = (DateTime?)RestResult.WorkItem[field]; - var soapValue = (DateTime)SoapResult.WorkItem[field]; + Action AssertAreEqual = (restValue, soapValue) => + { + try + { + restValue.ShouldEqual(soapValue, GenericComparer.Default); + } + catch (Exception e) + { + exceptions.Add(e); + } + }; - restValue.GetValueOrDefault().ShouldEqual(soapValue.ToUniversalTime(), field); - } - catch (Exception e) - { - exceptions.Add(e); - } + try + { + AssertAreEqual(RestResult.WorkItem.Id, SoapResult.WorkItem.Id); + AssertAreEqual(RestResult.WorkItem.Title, SoapResult.WorkItem.Title); + AssertAreEqual(RestResult.WorkItem.WorkItemType, SoapResult.WorkItem.WorkItemType); } - - if (exceptions.Any()) + catch (Exception e) { - throw new AggregateException(exceptions.EachToUsefulString(), exceptions); + exceptions.Add(e); } + + if (exceptions.Any()) throw new AggregateException(exceptions.EachToUsefulString(), exceptions); } [TestMethod] [TestCategory("localOnly")] [TestCategory("SOAP")] [TestCategory("REST")] - public void CoreFields_are_equal() + public void RelatedLinkCount_is_equal() + { + AssertWorkItemExpandConfiguration(); + + RestResult.WorkItem.RelatedLinkCount.ShouldEqual(SoapResult.WorkItem.RelatedLinkCount); + } + + private void AssertWorkItemExpandConfiguration() + { + if (RestResult.WorkItemStore.Configuration.WorkItemExpand == WorkItemExpand.None + || RestResult.WorkItemStore.Configuration.WorkItemExpand == WorkItemExpand.Fields) + Assert.Inconclusive("The links could not tested because the expand configuration was not set to include links."); + } + + private void GetCoreFieldComparisonAssertions(Func GetValue) { var exceptions = new List(); + if (Rest.Configuration.WorkItemExpand != WorkItemExpand.All && Rest.Configuration.WorkItemExpand != WorkItemExpand.Fields) + Assert.Inconclusive("REST configuration does not include all fields."); var fieldsWithKnownDifferences = new[] { @@ -167,21 +216,24 @@ public void CoreFields_are_equal() CoreFieldRefNames.ExternalLinkCount, CoreFieldRefNames.HyperlinkCount, CoreFieldRefNames.RelatedLinkCount, - CoreFieldRefNames.AssignedTo, CoreFieldRefNames.AuthorizedAs, - CoreFieldRefNames.ChangedBy, CoreFieldRefNames.CreatedBy + CoreFieldRefNames.AssignedTo, + CoreFieldRefNames.AuthorizedAs, + CoreFieldRefNames.ChangedBy, + CoreFieldRefNames.CreatedBy }; foreach (var field in CoreFieldRefNames.All.Except(fieldsWithKnownDifferences)) { try { - - var restValue = RestResult.WorkItem[field]?.ToString(); - var soapValue = SoapResult.WorkItem[field]?.ToString(); - - - restValue.ShouldEqual(soapValue, field); - + var restValue = GetValue(RestResult.WorkItem, field); + var soapValue = GetValue(SoapResult.WorkItem, field); + + // We do approximate equality here: + // - SOAP "fixes" values of certain types before returning, REST does not + if (string.IsNullOrEmpty(restValue) || string.IsNullOrEmpty(soapValue)) + string.IsNullOrEmpty(restValue).ShouldEqual(string.IsNullOrEmpty(soapValue), field); + else restValue.ShouldEqual(soapValue, field); } catch (Exception e) { @@ -189,31 +241,7 @@ public void CoreFields_are_equal() } } - if (exceptions.Any()) - { - throw new AggregateException(exceptions.EachToUsefulString(), exceptions); - } - } - - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - [TestCategory("REST")] - public void CreatedDate_is_equal() - { - RestResult.WorkItem.CreatedDate.ShouldEqual(SoapResult.WorkItem.CreatedDate.ToUniversalTime()); - } - - [TestMethod] - [TestCategory("localOnly")] - [TestCategory("SOAP")] - [TestCategory("REST")] - public void RelatedLinkCount_is_equal() - { - if (((Client.Rest.WorkItemStoreConfiguration)RestResult.WorkItemStore.Configuration).WorkItemExpand != WorkItemExpand.Links) - Assert.Inconclusive("The links could not tested because the expand configuration was not set."); - - RestResult.WorkItem.RelatedLinkCount.ShouldEqual(SoapResult.WorkItem.RelatedLinkCount); + if (exceptions.Any()) throw new AggregateException(exceptions.EachToUsefulString(), exceptions); } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs index cf08dbeb..67dd3d87 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/LinkTests.cs @@ -1,6 +1,5 @@ using System; -using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -14,7 +13,7 @@ public class Given_a_WorkItem_with_Links : WorkItemWithLinksContextSpecification public override void Given() { base.Given(); - ((Client.Rest.WorkItemStoreConfiguration)Rest.Configuration).WorkItemExpand = WorkItemExpand.Relations; + Rest.Configuration.WorkItemExpand = WorkItemExpand.All; } [TestMethod] @@ -71,8 +70,9 @@ public void RelatedLinkCount_is_equal() private void AssertWorkItemExpandConfiguration() { - if (((Client.Rest.WorkItemStoreConfiguration)RestResult.WorkItemStore.Configuration).WorkItemExpand != WorkItemExpand.Links) - Assert.Inconclusive("The links could not tested because the expand configuration was not set."); + if (RestResult.WorkItemStore.Configuration.WorkItemExpand == WorkItemExpand.None + || RestResult.WorkItemStore.Configuration.WorkItemExpand == WorkItemExpand.Fields) + Assert.Inconclusive("The links could not tested because the expand configuration was not set to include links."); } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index f368494c..2609836f 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -9,19 +9,16 @@ namespace Microsoft.Qwiq.Mocks { public class MockWorkItemStore : IWorkItemStore { - private static readonly Random Instance = new Random(); - - internal readonly IList LinkInfo; - internal readonly IDictionary _lookup; + internal readonly IList LinkInfo; + private static readonly Random Instance = new Random(); + private readonly Lazy _queryFactory; private readonly Lazy _storeDefinitions; - private Lazy _projects; - private readonly Lazy _tfs; - private readonly Lazy _queryFactory; + private Lazy _projects; public MockWorkItemStore() : this(() => new MockTfsTeamProjectCollection(), store => new MockQueryFactory(store)) @@ -36,10 +33,7 @@ public MockWorkItemStore(IEnumerable workItems, IEnumerable tpcFactory, - Func queryFactory - ) + public MockWorkItemStore(Func tpcFactory, Func queryFactory) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); @@ -48,31 +42,32 @@ Func queryFactory _queryFactory = new Lazy(() => queryFactory(this)); _projects = new Lazy(() => new MockProjectCollection(this)); - WorkItemLinkTypes = new WorkItemLinkTypeCollection(CoreLinkTypeReferenceNames.All.Select(s => (IWorkItemLinkType)new MockWorkItemLinkType(s)).ToList()); + WorkItemLinkTypes = new WorkItemLinkTypeCollection( + CoreLinkTypeReferenceNames + .All.Select(s => (IWorkItemLinkType)new MockWorkItemLinkType(s)) + .ToList()); _lookup = new Dictionary(); LinkInfo = new List(); _storeDefinitions = new Lazy(() => new MockFieldDefinitionCollection(this)); - Configuration = new WorkItemStoreConfiguration(); + Configuration = new MockWorkItemStoreConfiguration(); } - public bool SimulateQueryTimes { get; set; } - - private int WaitTime => Instance.Next(0, 3000); - public VssCredentials AuthorizedCredentials => null; - public IFieldDefinitionCollection FieldDefinitions => _storeDefinitions.Value; + /// + /// + public WorkItemStoreConfiguration Configuration { get; } + public IFieldDefinitionCollection FieldDefinitions => _storeDefinitions.Value; public IProjectCollection Projects => _projects.Value; - + public IRegisteredLinkTypeCollection RegisteredLinkTypes { get; } + public bool SimulateQueryTimes { get; set; } public ITeamProjectCollection TeamProjectCollection => _tfs.Value; - - public TimeZone TimeZone => _tfs.Value.TimeZone; - - public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection.AuthorizedIdentity; - public IWorkItemLinkTypeCollection WorkItemLinkTypes { get; internal set; } + public ITeamFoundationIdentity AuthorizedIdentity => TeamProjectCollection.AuthorizedIdentity; + public TimeZone TimeZone => _tfs.Value.TimeZone; + private int WaitTime => Instance.Next(0, 3000); public void Dispose() { @@ -94,8 +89,6 @@ public IWorkItemCollection Query(IEnumerable ids, DateTime? asOf = null) var ids2 = (int[])ids.ToArray().Clone(); if (!ids2.Any()) return Enumerable.Empty().ToWorkItemCollection(); - - Trace.TraceInformation("Querying for IDs " + string.Join(", ", ids2)); var query = _queryFactory.Value.Create(ids2, asOf); @@ -114,30 +107,14 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision return query.RunLinkQuery().ToList().AsReadOnly(); } - /// - public WorkItemStoreConfiguration Configuration { get; } - - public IRegisteredLinkTypeCollection RegisteredLinkTypes { get; } - - - internal void BatchSave(IEnumerable workItems) { - - // First: Fix up the work items and save them to our dictionary - foreach (var item in workItems) - { - Save(item); - } + foreach (var item in workItems) Save(item); - // Second: Update the links for the work items - // We need to save first so we can create recipricol links if required (e.g. parent -> child also needs a child -> parent) - foreach (var item in workItems) - { - SaveLinks(item); - } + // Second: Update the links for the work items We need to save first so we can create recipricol links if required (e.g. parent -> child also needs a child -> parent) + foreach (var item in workItems) SaveLinks(item); // Third: If any of the work items have types that are missing from their project, add those var missingWits = new Dictionary>(); @@ -148,7 +125,7 @@ internal void BatchSave(IEnumerable workItems) IProject project; try { - project = Projects[projectName]; + project = Projects[projectName]; } catch (DeniedOrNotExistException) { @@ -157,16 +134,15 @@ internal void BatchSave(IEnumerable workItems) } if (!project.WorkItemTypes.Contains(witName)) - { - Trace.TraceWarning("Project {0} is missing work item type definition {1}", project, witName); - missingWits.TryAdd(project, new HashSet(WorkItemTypeComparer.Default)); - - var t = item.Type as MockWorkItemType; - if (t?.Store != this) t.Store = this; + { + Trace.TraceWarning("Project {0} is missing work item type definition {1}", project, witName); + missingWits.TryAdd(project, new HashSet(WorkItemTypeComparer.Default)); - missingWits[project].Add(item.Type); - } + var t = item.Type as MockWorkItemType; + if (t?.Store != this) t.Store = this; + missingWits[project].Add(item.Type); + } } // Fourth: If there are any missing wits update the project and reset the project collection @@ -193,29 +169,25 @@ internal void BatchSave(IEnumerable workItems) changesRequired = true; wits.UnionWith(project.WorkItemTypes); var w = new WorkItemTypeCollection(wits.ToList()); - var p = new MockProject( - project.Guid, - project.Name, - project.Uri, - w, - project.AreaRootNodes, - project.IterationRootNodes); + var p = new MockProject(project.Guid, project.Name, project.Uri, w, project.AreaRootNodes, project.IterationRootNodes); newProjects.Add(p); } } - - if (changesRequired) _projects = new Lazy(() => new MockProjectCollection(newProjects)); } - private void Save(IWorkItem item) + protected void Dispose(bool disposing) { - // Fix the ID - if (item.Id == 0) + if (disposing) { - item[CoreFieldRefNames.Id] = _lookup.Keys.Any() ? _lookup.Keys.Max() + 1 : 1; } + } + + private void Save(IWorkItem item) + { + // Fix the ID + if (item.Id == 0) item[CoreFieldRefNames.Id] = _lookup.Keys.Any() ? _lookup.Keys.Max() + 1 : 1; var id = item.Id; @@ -226,13 +198,7 @@ private void Save(IWorkItem item) { BaseLinkType.Hyperlink, 0 } }; - if (item.Links != null && item.Links.Any()) - { - foreach (var link in item.Links) - { - l[link.BaseType]++; - } - } + if (item.Links != null && item.Links.Any()) foreach (var link in item.Links) l[link.BaseType]++; item[CoreFieldRefNames.RelatedLinkCount] = l[BaseLinkType.RelatedLink]; item[CoreFieldRefNames.ExternalLinkCount] = l[BaseLinkType.ExternalLink]; @@ -248,19 +214,6 @@ private void Save(IWorkItem item) _lookup[id] = item; } - private void SaveLinks(IWorkItem item) - { - var id = item.Id; - // If there are new links add them back - if (item.Links != null && item.Links.Any()) - { - foreach (var link in item.Links) - { - SaveLink(link, id); - } - } - } - private void SaveLink(ILink link, int id) { // We only support related links at the moment @@ -272,20 +225,14 @@ private void SaveLink(ILink link, int id) { var li = mrl.LinkInfo; if (LinkInfo.Contains(li, WorkItemLinkInfoComparer.Default)) - { Trace.TraceWarning( $"Warning: Duplicate link. (Type: {li.LinkType?.ImmutableName ?? "NULL"}; Source: {li.SourceId}; Target: {li.TargetId})"); - } - else - { - LinkInfo.Add(li); - } + else LinkInfo.Add(li); if (rl.LinkTypeEnd == null) return; // Check to see if a recipricol link is required if (rl.LinkTypeEnd.LinkType.IsDirectional) - { try { var t = _lookup[rl.RelatedWorkItemId]; @@ -302,10 +249,9 @@ private void SaveLink(ILink link, int id) } catch (KeyNotFoundException) { - Trace.TraceWarning($"Work item {id} contains a {rl.LinkTypeEnd} to an item that does not exist: {rl.RelatedWorkItemId}."); - + Trace.TraceWarning( + $"Work item {id} contains a {rl.LinkTypeEnd} to an item that does not exist: {rl.RelatedWorkItemId}."); } - } } else { @@ -313,11 +259,11 @@ private void SaveLink(ILink link, int id) } } - protected void Dispose(bool disposing) + private void SaveLinks(IWorkItem item) { - if (disposing) - { - } + var id = item.Id; + // If there are new links add them back + if (item.Links != null && item.Links.Any()) foreach (var link in item.Links) SaveLink(link, id); } } } \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemStoreConfiguration.cs b/test/Qwiq.Mocks/MockWorkItemStoreConfiguration.cs new file mode 100644 index 00000000..dc5f008f --- /dev/null +++ b/test/Qwiq.Mocks/MockWorkItemStoreConfiguration.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Microsoft.Qwiq.Mocks +{ + public class MockWorkItemStoreConfiguration : WorkItemStoreConfiguration + { + /// + /// + public override IEnumerable DefaultFields { get; set; } + + /// + /// + public override WorkItemErrorPolicy WorkItemErrorPolicy { get; set; } + + /// + /// + public override WorkItemExpand WorkItemExpand { get; set; } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 0ed5ed2e..5b96535e 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -60,6 +60,7 @@ + From 50371680d352e54e2a02a91687186b0acf83ae67 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 17:45:14 -0700 Subject: [PATCH 211/251] Make `.ctor` of `WorkItemCollection` public --- src/Qwiq.Core/WorkItemCollection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core/WorkItemCollection.cs b/src/Qwiq.Core/WorkItemCollection.cs index 6a96d017..42224621 100644 --- a/src/Qwiq.Core/WorkItemCollection.cs +++ b/src/Qwiq.Core/WorkItemCollection.cs @@ -5,12 +5,12 @@ namespace Microsoft.Qwiq { public class WorkItemCollection : ReadOnlyObjectWithIdCollection, IWorkItemCollection { - internal WorkItemCollection([CanBeNull] List workItems) + public WorkItemCollection([CanBeNull] List workItems) : base(workItems) { } - internal WorkItemCollection([CanBeNull] IEnumerable workItems) + public WorkItemCollection([CanBeNull] IEnumerable workItems) : base(workItems) { } From 85c1afe597f34bf75c1b4e34cb5efa165da77d01 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 17:45:45 -0700 Subject: [PATCH 212/251] Null out reference after dispose --- src/Qwiq.Core.Rest/WorkItemStore.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 4dfb1951..34bcc6d5 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -68,7 +68,7 @@ internal WorkItemStore( public IWorkItemLinkTypeCollection WorkItemLinkTypes => _linkTypes ?? (_linkTypes = WorkItemLinkTypeCollectionFactory()); - internal Lazy NativeWorkItemStore { get; } + internal Lazy NativeWorkItemStore { get; private set; } public void Dispose() { @@ -182,7 +182,13 @@ private static WorkItemLinkTypeCollection GetLinks(WorkItemTrackingHttpClient wo private void Dispose(bool disposing) { - if (disposing) if (NativeWorkItemStore.IsValueCreated) NativeWorkItemStore.Value?.Dispose(); + if (disposing) + { + if (NativeWorkItemStore.IsValueCreated) NativeWorkItemStore.Value?.Dispose(); + NativeWorkItemStore = null; + + + } } private IProjectCollection ProjectCollectionFactory() From 4c3a53a449566459f90abb8f987019d629207245 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 17:46:11 -0700 Subject: [PATCH 213/251] Annotate extension methods for identity --- src/Qwiq.Identity.Soap/Extensions.cs | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/Qwiq.Identity.Soap/Extensions.cs b/src/Qwiq.Identity.Soap/Extensions.cs index 96972117..1a9bf4af 100644 --- a/src/Qwiq.Identity.Soap/Extensions.cs +++ b/src/Qwiq.Identity.Soap/Extensions.cs @@ -1,4 +1,9 @@ -using Microsoft.Qwiq.Client.Soap; +using System; +using System.Diagnostics.Contracts; + +using JetBrains.Annotations; + +using Microsoft.Qwiq.Client.Soap; using Microsoft.Qwiq.Exceptions; using Microsoft.TeamFoundation.Framework.Client; @@ -6,26 +11,39 @@ namespace Microsoft.Qwiq.Identity.Soap { public static class Extensions { - internal static IIdentityDescriptor AsProxy(this TeamFoundation.Framework.Client.IdentityDescriptor descriptor) + [NotNull] + [JetBrains.Annotations.Pure] + public static IIdentityManagementService GetIdentityManagementService([NotNull] this ITeamProjectCollection tpc) { - return ExceptionHandlingDynamicProxyFactory.Create(new Client.Soap.IdentityDescriptor(descriptor)); + Contract.Requires(tpc != null); + + if (tpc == null) throw new ArgumentNullException(nameof(tpc)); + return ((IInternalTeamProjectCollection)tpc).GetService().AsProxy(); } - internal static IIdentityManagementService AsProxy(this IIdentityManagementService2 ims) + [NotNull] + [JetBrains.Annotations.Pure] + public static IIdentityManagementService GetIdentityManagementService([NotNull] this IWorkItemStore wis) { - return ims == null - ? null - : ExceptionHandlingDynamicProxyFactory.Create(new IdentityManagementService(ims)); + if (wis == null) throw new ArgumentNullException(nameof(wis)); + return wis.TeamProjectCollection.GetIdentityManagementService(); } - public static IIdentityManagementService GetIdentityManagementService(this ITeamProjectCollection tpc) + [NotNull] + [JetBrains.Annotations.Pure] + internal static IIdentityDescriptor AsProxy([NotNull] this TeamFoundation.Framework.Client.IdentityDescriptor descriptor) { - return ((IInternalTeamProjectCollection)tpc).GetService().AsProxy(); + if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); + return ExceptionHandlingDynamicProxyFactory.Create(new Client.Soap.IdentityDescriptor(descriptor)); } - public static IIdentityManagementService GetIdentityManagementService(this IWorkItemStore wis) + [JetBrains.Annotations.Pure] + [CanBeNull] + internal static IIdentityManagementService AsProxy([CanBeNull] this IIdentityManagementService2 ims) { - return wis.TeamProjectCollection.GetIdentityManagementService(); + return ims == null + ? null + : ExceptionHandlingDynamicProxyFactory.Create(new IdentityManagementService(ims)); } } -} +} \ No newline at end of file From 9a4cda3f83e987ef1715d2b75747d2c0368a53ee Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 17:46:36 -0700 Subject: [PATCH 214/251] Add .nuspec for identity soap --- src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj | 1 + src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.nuspec | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.nuspec diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj index 9413f743..eb6accb1 100644 --- a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj @@ -179,6 +179,7 @@ + diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.nuspec b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.nuspec new file mode 100644 index 00000000..73db79d3 --- /dev/null +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.nuspec @@ -0,0 +1,16 @@ + + + + $id$ + $version$ + $title$ + Microsoft + Microsoft, QWIQ + https://github.com/MicrosoftEdge/Microsoft.Qwiq + https://github.com/MicrosoftEdge/Microsoft.Qwiq/blob/master/LICENSE + true + $description$ + $copyright$ + Microsoft Team Foundation Server TFS VSO Visual Studio Online VisualStudio Agile WIT Work Item Tracking Object Model VSTS TeamFoundation TFSOM + + \ No newline at end of file From 7e434b512eeb9b635fdcd9a60ee61768418a8c0e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 17:46:56 -0700 Subject: [PATCH 215/251] Correct assembly description for identity soap --- src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs b/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs index 1fcfadef..f71a402a 100644 --- a/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Identity.Soap/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.Qwiq.Identity.Soap")] -[assembly: AssemblyDescription("Provides identity extension methods on top of Microsoft.Qwiq.Core")] +[assembly: AssemblyDescription("Provides identity extension methods on top of Microsoft.Qwiq.Client.Soap")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("2b588d8c-5e01-4b48-96a7-b961fc54a4ac")] From 9bedfd493109e71e8b35a2c1c6f12b01b294c2ff Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Thu, 4 May 2017 17:55:18 -0700 Subject: [PATCH 216/251] Add extension methods from WebJobs --- src/Qwiq.Core/IWorkItem.Extensions.cs | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/Qwiq.Core/IWorkItem.Extensions.cs b/src/Qwiq.Core/IWorkItem.Extensions.cs index 7a3c6ec9..a022ac9c 100644 --- a/src/Qwiq.Core/IWorkItem.Extensions.cs +++ b/src/Qwiq.Core/IWorkItem.Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using JetBrains.Annotations; @@ -8,6 +9,69 @@ namespace Microsoft.Qwiq { public static partial class Extensions { + [PublicAPI] + public static void AddRelatedLink([NotNull] this IWorkItem workItem, [NotNull] IWorkItemStore store, int targetId) + { + Contract.Requires(workItem != null); + Contract.Requires(store != null); + Contract.Requires(targetId > 0); + + if (workItem == null) throw new ArgumentNullException(nameof(workItem)); + if (store == null) throw new ArgumentNullException(nameof(store)); + if (targetId == 0) throw new ArgumentOutOfRangeException(nameof(targetId)); + + var end = store.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related].ForwardEnd; + workItem.Links.Add(workItem.CreateRelatedLink(targetId, end)); + } + + [PublicAPI] + public static void AddParentLink([NotNull] this IWorkItem workItem, [NotNull] IWorkItemStore store, int parentId) + { + Contract.Requires(workItem != null); + Contract.Requires(store != null); + Contract.Requires(parentId > 0); + + if (workItem == null) throw new ArgumentNullException(nameof(workItem)); + if (store == null) throw new ArgumentNullException(nameof(store)); + if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); + + var end = store.GetParentLinkTypeEnd(); + workItem.Links.Add(workItem.CreateRelatedLink(parentId, end)); + } + + [PublicAPI] + public static void AddChildLink([NotNull] this IWorkItem workItem, [NotNull] IWorkItemStore store, int childId) + { + Contract.Requires(workItem != null); + Contract.Requires(store != null); + Contract.Requires(childId > 0); + + if (workItem == null) throw new ArgumentNullException(nameof(workItem)); + if (store == null) throw new ArgumentNullException(nameof(store)); + if (childId == 0) throw new ArgumentOutOfRangeException(nameof(childId)); + + var end = store.GetChildLinkTypeEnd(); + workItem.Links.Add(workItem.CreateRelatedLink(childId, end)); + } + + [PublicAPI] + public static void AddChildrenLink([NotNull] this IWorkItem workItem, [NotNull] IWorkItemStore store, [NotNull] params int[] childrenIds) + { + Contract.Requires(workItem != null); + Contract.Requires(store != null); + Contract.Requires(childrenIds != null); + Contract.Requires(childrenIds.Length > 0); + + if (workItem == null) throw new ArgumentNullException(nameof(workItem)); + if (store == null) throw new ArgumentNullException(nameof(store)); + if (childrenIds == null) throw new ArgumentNullException(nameof(childrenIds)); + if (childrenIds.Length == 0) throw new ArgumentNullException(nameof(childrenIds)); + + var end = store.GetChildLinkTypeEnd(); + foreach(var id in childrenIds) workItem.Links.Add(workItem.CreateRelatedLink(id, end)); + } + + [PublicAPI] public static void AddRelatedLink(this IWorkItem workItem, IWorkItemStore store, int[] targets) { if (workItem == null) throw new ArgumentNullException(nameof(workItem)); @@ -19,6 +83,7 @@ public static void AddRelatedLink(this IWorkItem workItem, IWorkItemStore store, foreach (var id in targets) workItem.Links.Add(workItem.CreateRelatedLink(id, end)); } + [PublicAPI] public static IWorkItemCollection ToWorkItemCollection([NotNull] [ItemNotNull] [InstantHandle] this IEnumerable items) { if (items == null) throw new ArgumentNullException(nameof(items)); From af50cb4ce746c8c31c6da63177d910a80b3a8146 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 14:58:26 -0700 Subject: [PATCH 217/251] Add store fix in Mock WIS When "saving" a work item, check the store on the WIT and set the value to the current WIS if necessary. --- test/Qwiq.Mocks/MockWorkItemStore.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index 2609836f..b1154b69 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -211,6 +211,16 @@ private void Save(IWorkItem item) projectName = Projects[0].Name; item[CoreFieldRefNames.TeamProject] = projectName; } + + // Fix up Store if needed + if (item.Type is MockWorkItemType t) + { + if (t.Store == null || t.Store != this) + { + t.Store = this; + } + } + _lookup[id] = item; } From be642cef2aa7298971734c169ecd681a1d332a54 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 14:58:49 -0700 Subject: [PATCH 218/251] Add JetBrains annotations --- test/Qwiq.Mocks/MockProjectCollection.cs | 28 +++++------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/test/Qwiq.Mocks/MockProjectCollection.cs b/test/Qwiq.Mocks/MockProjectCollection.cs index d2c9128c..5f23cdd6 100644 --- a/test/Qwiq.Mocks/MockProjectCollection.cs +++ b/test/Qwiq.Mocks/MockProjectCollection.cs @@ -1,41 +1,25 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Mocks { internal class MockProjectCollection : ProjectCollection { - public MockProjectCollection(IWorkItemStore store) + public MockProjectCollection([NotNull] IWorkItemStore store) : this(new MockProject(store)) { } - public MockProjectCollection(MockProject project) + public MockProjectCollection([NotNull] MockProject project) : this(new[] { (IProject)project }.ToList()) { } - public MockProjectCollection(List projects) + public MockProjectCollection([NotNull] List projects) : base(projects) { } } - - internal class MockWorkItemTypeCollection : WorkItemTypeCollection - { - public MockWorkItemTypeCollection(IWorkItemStore store) - : base((List)null) - { - ItemFactory = () => new[] - { - new MockWorkItemType(WorkItemTypeDefinitions.Task, null, store), - new MockWorkItemType(WorkItemTypeDefinitions.Deliverable, null, store), - new MockWorkItemType(WorkItemTypeDefinitions.Scenario, null, store), - new MockWorkItemType(WorkItemTypeDefinitions.CustomerPromise, null, store), - new MockWorkItemType(WorkItemTypeDefinitions.Bug, null, store), - new MockWorkItemType(WorkItemTypeDefinitions.Measure, null, store) - }; - } - } } \ No newline at end of file From f6c1485d2b6d61f45075aa3520e62716472c7aa2 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:00:12 -0700 Subject: [PATCH 219/251] Add ctor overloads for MockIdentityDescriptor for UPN --- test/Qwiq.Mocks/MockIdentityDescriptor.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/Qwiq.Mocks/MockIdentityDescriptor.cs b/test/Qwiq.Mocks/MockIdentityDescriptor.cs index 4a303b08..68899a70 100644 --- a/test/Qwiq.Mocks/MockIdentityDescriptor.cs +++ b/test/Qwiq.Mocks/MockIdentityDescriptor.cs @@ -8,13 +8,19 @@ public class MockIdentityDescriptor public const string TenantId = "CD4C5751-F4E6-41D5-A4C9-EFFD66BC8E9C"; + public static IIdentityDescriptor Create(string userPrincipalName) + { + return Create(userPrincipalName, TenantId); + } + public static IIdentityDescriptor Create(string userPrincipalName, string tenantId) + { + return new IdentityDescriptor(IdentityConstants.ClaimsType, $"{tenantId}\\{userPrincipalName}"); + } public static IIdentityDescriptor Create(string alias, string domain = Domain, string tenantId = TenantId) { return new IdentityDescriptor(IdentityConstants.ClaimsType, $"{tenantId}\\{alias}@{domain}"); } } - - } \ No newline at end of file From 972f92f98e98138ffef2b8aae6368ee2ce41e0df Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:02:18 -0700 Subject: [PATCH 220/251] Add methods to set field values on MockFieldCollection Useful for arranging conditions where fields are dirty or invalid --- test/Qwiq.Mocks/MockFieldCollection.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/Qwiq.Mocks/MockFieldCollection.cs b/test/Qwiq.Mocks/MockFieldCollection.cs index 18fd8ffc..8d220bc8 100644 --- a/test/Qwiq.Mocks/MockFieldCollection.cs +++ b/test/Qwiq.Mocks/MockFieldCollection.cs @@ -1,10 +1,24 @@ +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Mocks { public class MockFieldCollection : FieldCollection { - public MockFieldCollection(WorkItemCore w, IFieldDefinitionCollection definitions) + public MockFieldCollection([NotNull] WorkItemCore w, [NotNull] IFieldDefinitionCollection definitions) : base(w, definitions, (r, d) => new MockField(r, d)) { } + + public new void SetField([NotNull] IField field) + { + base.SetField(field); + } + + public void SetFieldValue([NotNull] string name, [CanBeNull] object value) + { + TryGetByName(name, out IField f); + f.Value = value; + SetField(f); + } } } \ No newline at end of file From 824a09eacfa5afed27cd254d60badb616634f098 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:03:41 -0700 Subject: [PATCH 221/251] Add "attaching" MockField to work item A field could be created in dependently of a work item, but the values set in the field would not be reflected in the work item itself. New property added to set the work item and copy any existing value to the WI --- test/Qwiq.Mocks/MockField.cs | 84 ++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/test/Qwiq.Mocks/MockField.cs b/test/Qwiq.Mocks/MockField.cs index 0e0adf81..2f91ecb7 100644 --- a/test/Qwiq.Mocks/MockField.cs +++ b/test/Qwiq.Mocks/MockField.cs @@ -1,30 +1,19 @@ using System; +using System.Diagnostics; namespace Microsoft.Qwiq.Mocks { public class MockField : IField { - private readonly IRevisionInternal _revision; - - private readonly IFieldDefinition _fieldDefinition; - private const int MaxStringLength = 255; - private object _value; - - private ValidationState _validationState; + private IRevisionInternal _revision; - private bool _isDirty; + private object _value; public MockField(IFieldDefinition fieldDefinition) { - _fieldDefinition = fieldDefinition; - } - - internal MockField(IRevisionInternal revision, IFieldDefinition definition) - :this(definition) - { - _revision = revision; + FieldDefinition = fieldDefinition; } public MockField( @@ -37,7 +26,7 @@ public MockField( { Value = value; OriginalValue = originalValue; - _validationState = validationState; + ValidationState = validationState; IsChangedByUser = isChangedByUser; IsEditable = true; } @@ -48,57 +37,71 @@ public MockField( object originalValue = null, ValidationState validationState = ValidationState.Valid, bool isChangedByUser = true) - :this(MockFieldDefinition.Create(Guid.NewGuid().ToString("N")), value, originalValue, validationState, isChangedByUser) + : this(MockFieldDefinition.Create(Guid.NewGuid().ToString("N")), value, originalValue, validationState, isChangedByUser) { } + internal MockField(IRevisionInternal revision, IFieldDefinition definition) + : this(definition) + { + Revision = revision; + } + + public IFieldDefinition FieldDefinition { get; } + public virtual int Id => FieldDefinition.Id; public bool IsChangedByUser { get; } - public bool IsDirty => _isDirty; + public bool IsDirty { get; private set; } - public bool IsEditable { get; } + public bool IsEditable { get; } public bool IsRequired { get; } public virtual bool IsValid => ValidationState == ValidationState.Valid; - public virtual string Name => _fieldDefinition.Name; - - public virtual string ReferenceName => _fieldDefinition.ReferenceName; - - public virtual int Id => _fieldDefinition.Id; + public virtual string Name => FieldDefinition.Name; public object OriginalValue { get; set; } - public ValidationState ValidationState => _validationState; + public virtual string ReferenceName => FieldDefinition.ReferenceName; + + public ValidationState ValidationState { get; private set; } public object Value { - get => _revision != null ? _revision.GetCurrentFieldValue(_fieldDefinition) : _value; + get => Revision != null ? Revision.GetCurrentFieldValue(FieldDefinition) : _value; set { - if (_revision != null) _revision.SetFieldValue(_fieldDefinition, value); - else _value = value; + if (Revision != null) + { + Revision.SetFieldValue(FieldDefinition, value); + } + else + { + Trace.TraceWarning( + $"Value will be local to this field only. Use the {nameof(Revision)} property to attach a work item."); + _value = value; + } - _isDirty = true; + IsDirty = true; if (OriginalValue != null && _value != null && _value.Equals(OriginalValue)) { - _isDirty = false; - _validationState = ValidationState.Valid; + IsDirty = false; + ValidationState = ValidationState.Valid; } switch (ValidationState) { case ValidationState.InvalidNotEmpty: - if (string.IsNullOrEmpty(value as string)) _validationState = ValidationState.Valid; + if (string.IsNullOrEmpty(value as string)) ValidationState = ValidationState.Valid; break; case ValidationState.InvalidTooLong: var str = value as string; - if (str != null && str.Length <= MaxStringLength) _validationState = ValidationState.Valid; + if (str != null && str.Length <= MaxStringLength) ValidationState = ValidationState.Valid; break; case ValidationState.Valid: @@ -154,5 +157,20 @@ public object Value } } } + + internal IRevisionInternal Revision + { + get => _revision; + set + { + if (_revision != null && _revision != value) throw new InvalidOperationException("Revision already set"); + _revision = value; + if (_value != null) + { + _revision.SetFieldValue(FieldDefinition, _value); + _value = null; + } + } + } } } \ No newline at end of file From 1573c4f8145a70f62f7b0147ccb7422913339a35 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:04:30 -0700 Subject: [PATCH 222/251] New ctor on MockIdentityManagementService permitting params of ITeamFoundationIdentity --- .../MockIdentityManagementService.cs | 85 ++++++++++++------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/test/Qwiq.Mocks/MockIdentityManagementService.cs b/test/Qwiq.Mocks/MockIdentityManagementService.cs index 5b78fd8f..9c7e2f71 100644 --- a/test/Qwiq.Mocks/MockIdentityManagementService.cs +++ b/test/Qwiq.Mocks/MockIdentityManagementService.cs @@ -39,6 +39,11 @@ public MockIdentityManagementService() { } + public MockIdentityManagementService(params ITeamFoundationIdentity[] identities) + : this(identities as IEnumerable) + { + } + public MockIdentityManagementService(IEnumerable identities) : this(identities.ToDictionary(k => new IdentityFieldValue(k).LogonName, e => e, StringComparer.OrdinalIgnoreCase)) { @@ -83,12 +88,11 @@ public MockIdentityManagementService(IDictionary userMappings) : this( userMappings.ToDictionary( kvp => kvp.Key, - kvp => new MockTeamFoundationIdentity( - kvp.Value, - kvp.Key - + "@" - + MockIdentityDescriptor - .Domain) as ITeamFoundationIdentity)) + kvp => (ITeamFoundationIdentity)new MockTeamFoundationIdentity( + MockIdentityDescriptor + .Create(kvp.Key), + kvp.Value, + Guid.Empty))) { } @@ -112,7 +116,9 @@ public IIdentityDescriptor CreateIdentityDescriptor(string identityType, string /// /// Read identities for given descriptors. /// - /// Collection of + /// + /// Collection of + /// /// /// An array of , corresponding 1 to 1 with input descriptor array. /// @@ -157,25 +163,34 @@ public IEnumerable>> R } } - private IEnumerable>> SearchByDisplayName(IEnumerable searchFactors) + /// + public ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue) { - // TFS Matches, ignoring case, the property "Display Name" on the identity + return ReadIdentities(searchFactor, new[] { searchFactorValue }).First().Value.SingleOrDefault(); + } - return searchFactors.Select(searchFactor => - { - bool Predicate(ITeamFoundationIdentity identity) => Comparer.OrdinalIgnoreCase.Equals(identity.DisplayName, searchFactor); - return new KeyValuePair>(searchFactor, Locate(Predicate)); - }); + private IEnumerable Locate(Func predicate) + { + return _accountNameMappings.Values.SelectMany(a => a, (a, i) => new { a, i }).Where(t => predicate(t.i)).Select(t => t.i) + .ToArray(); } - private IEnumerable>> SearchByAccountName(IEnumerable searchFactors) + private IEnumerable>> SearchByAccountName( + IEnumerable searchFactors) { // TFS Matches, ignoring case, the property "Account", which is in the property bag of the identity - return searchFactors.Select(searchFactor => + return searchFactors.Select( + searchFactor => { - bool Predicate(ITeamFoundationIdentity identity) => Comparer.OrdinalIgnoreCase.Equals(identity.GetUserAccountName(), searchFactor); - return new KeyValuePair>(searchFactor, Locate(Predicate)); + bool Predicate(ITeamFoundationIdentity identity) + { + return Comparer.OrdinalIgnoreCase.Equals(identity.GetUserAccountName(), searchFactor); + } + + return new KeyValuePair>( + searchFactor, + Locate(Predicate)); }); } @@ -187,23 +202,33 @@ private IEnumerable>> foreach (var searchFactor in searchFactors) { if (_accountNameMappings.ContainsKey(searchFactor)) - yield return new KeyValuePair>(searchFactor, _accountNameMappings[searchFactor]); - else yield return new KeyValuePair>(searchFactor, new ITeamFoundationIdentity[0]); + yield return new KeyValuePair>( + searchFactor, + _accountNameMappings[searchFactor]); + else + yield return new KeyValuePair>( + searchFactor, + new ITeamFoundationIdentity[0]); } } - /// - public ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue) + private IEnumerable>> SearchByDisplayName( + IEnumerable searchFactors) { - return ReadIdentities(searchFactor, new[] { searchFactorValue }).First().Value.SingleOrDefault(); - } + // TFS Matches, ignoring case, the property "Display Name" on the identity - private IEnumerable Locate(Func predicate) - { - return _accountNameMappings.Values.SelectMany(a => a, (a, i) => new { a, i }) - .Where(t => predicate(t.i)) - .Select(t => t.i) - .ToArray(); + return searchFactors.Select( + searchFactor => + { + bool Predicate(ITeamFoundationIdentity identity) + { + return Comparer.OrdinalIgnoreCase.Equals(identity.DisplayName, searchFactor); + } + + return new KeyValuePair>( + searchFactor, + Locate(Predicate)); + }); } } } \ No newline at end of file From cd33e7ec22c25aaf0a1deda0ccb16185adbf2b51 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:05:18 -0700 Subject: [PATCH 223/251] Code clean up for Mock Extensions --- test/Qwiq.Mocks/Extensions.cs | 83 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/test/Qwiq.Mocks/Extensions.cs b/test/Qwiq.Mocks/Extensions.cs index 0adc4d32..cfc3444d 100644 --- a/test/Qwiq.Mocks/Extensions.cs +++ b/test/Qwiq.Mocks/Extensions.cs @@ -2,44 +2,12 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Mocks { - - public static partial class Extensions { - internal static void BatchSave(this MockWorkItemStore store, params IWorkItem[] workItems) - { - store.BatchSave(workItems); - } - - public static MockWorkItem Create(this MockWorkItemStore store) - { - return Create(store, null); - } - - public static MockWorkItem Create(this MockWorkItemStore store, IEnumerable> values = null) - { - var project = store.Projects[0]; - var wit = project.WorkItemTypes[0]; - - var tp = new KeyValuePair(CoreFieldRefNames.TeamProject, project.Name); - var wp = new KeyValuePair(CoreFieldRefNames.WorkItemType, wit.Name); - var a = new[] { tp, wp }; - - values = values?.Union(a) ?? a; - - var wi = (MockWorkItem)wit.NewWorkItem(values); - store.BatchSave(wi); - return (MockWorkItem)wi; - } - - public static MockWorkItem Generate(this MockWorkItemStore store) - { - var g = new WorkItemGenerator(store.Create, new []{"Revisions", "Item"}); - return g.Generate(1).Single(); - } - public static MockWorkItemStore Add(this MockWorkItemStore store, IEnumerable workItems) { return store.Add(workItems, null); @@ -98,12 +66,6 @@ public static MockWorkItemStore Add( return store; } - public static MockWorkItemStore WithLinkType(this MockWorkItemStore store, params IWorkItemLinkType[] linkTypes) - { - store.WorkItemLinkTypes = new WorkItemLinkTypeCollection(store.WorkItemLinkTypes.Union(linkTypes).ToList()); - return store; - } - public static MockWorkItemStore AddChildLink(this MockWorkItemStore store, int parentId, int childId) { var child = store.Query(childId); @@ -116,12 +78,49 @@ public static MockWorkItemStore AddChildLink(this MockWorkItemStore store, int p return store; } - public static IWorkItemStore Store(this IWorkItemType type) + public static MockWorkItem Create(this MockWorkItemStore store) + { + return Create(store, null); + } + + public static MockWorkItem Create(this MockWorkItemStore store, IEnumerable> values = null) { - if (type == null) return null; + var project = store.Projects[0]; + var wit = project.WorkItemTypes[0]; + var tp = new KeyValuePair(CoreFieldRefNames.TeamProject, project.Name); + var wp = new KeyValuePair(CoreFieldRefNames.WorkItemType, wit.Name); + var a = new[] { tp, wp }; + + values = values?.Union(a) ?? a; + + var wi = (MockWorkItem)wit.NewWorkItem(values); + store.BatchSave(wi); + return wi; + } + + public static MockWorkItem Generate(this MockWorkItemStore store) + { + var g = new WorkItemGenerator(store.Create, new[] { "Revisions", "Item" }); + return g.Generate(1).Single(); + } + + [CanBeNull] + public static IWorkItemStore Store([CanBeNull] this IWorkItemType type) + { var t = type as MockWorkItemType; return t?.Store; } + + public static MockWorkItemStore WithLinkType(this MockWorkItemStore store, params IWorkItemLinkType[] linkTypes) + { + store.WorkItemLinkTypes = new WorkItemLinkTypeCollection(store.WorkItemLinkTypes.Union(linkTypes).ToList()); + return store; + } + + internal static void BatchSave(this MockWorkItemStore store, params IWorkItem[] workItems) + { + store.BatchSave(workItems); + } } } \ No newline at end of file From 552e79e1608c8e4d367b24d1e6ec9c8642979868 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:05:40 -0700 Subject: [PATCH 224/251] Move nested types to own files --- test/Qwiq.Mocks/MockRevision.cs | 31 +++++ test/Qwiq.Mocks/MockWorkItem.cs | 109 +++++++++++++++--- test/Qwiq.Mocks/MockWorkItemTypeCollection.cs | 23 ++++ test/Qwiq.Mocks/Qwiq.Mocks.csproj | 2 + 4 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 test/Qwiq.Mocks/MockRevision.cs create mode 100644 test/Qwiq.Mocks/MockWorkItemTypeCollection.cs diff --git a/test/Qwiq.Mocks/MockRevision.cs b/test/Qwiq.Mocks/MockRevision.cs new file mode 100644 index 00000000..8287e068 --- /dev/null +++ b/test/Qwiq.Mocks/MockRevision.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +using JetBrains.Annotations; + +namespace Microsoft.Qwiq.Mocks +{ + [PublicAPI] + public class MockRevision : Revision + { + public MockRevision([NotNull] Dictionary dictionary, int index) + :base(new MockFieldDefinitionCollection(dictionary.Keys.Select(MockFieldDefinition.Create)), index) + { + Contract.Requires(dictionary != null); + Contract.Requires(index > 0); + + foreach (var kvp in dictionary) + { + var fd = FieldDefinitions[kvp.Key]; + SetFieldValue(fd.Id, kvp.Value); + } + } + + public MockRevision([NotNull] Dictionary dictionary) + : this(dictionary, (int)dictionary["Index"]) + { + + } + } +} diff --git a/test/Qwiq.Mocks/MockWorkItem.cs b/test/Qwiq.Mocks/MockWorkItem.cs index 997e2fe2..8c469445 100644 --- a/test/Qwiq.Mocks/MockWorkItem.cs +++ b/test/Qwiq.Mocks/MockWorkItem.cs @@ -1,10 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; -using System.IO; using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; using JetBrains.Annotations; @@ -13,10 +13,16 @@ namespace Microsoft.Qwiq.Mocks [Serializable] public class MockWorkItem : WorkItem, IWorkItem { + private static int tempId = 0; + private IFieldCollection _fields; internal bool PartialOpenWasCalled; + private IEnumerable _revisions; + + private int _tempId; + [DebuggerStepThrough] [Obsolete( "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] @@ -29,7 +35,7 @@ public MockWorkItem() [Obsolete( "This method has been deprecated and will be removed in a future release. See a constructor that takes IWorkItemType and fields.")] public MockWorkItem(string workItemType = null) - : this(workItemType, null) + : this(workItemType, (IDictionary)null) { } @@ -54,6 +60,28 @@ public MockWorkItem(string workItemType = null, IDictionary fiel { } + public MockWorkItem([CanBeNull] string workItemType, [CanBeNull] params IField[] fields) + : this(new MockWorkItemType(workItemType ?? "Mock", CoreFieldDefinitions.All.Union(fields.Select(f=>f.FieldDefinition)))) + { + if (fields == null) return; + + if (Fields is MockFieldCollection c) + { + foreach (var field in fields) + { + c.SetField(field); + if (field is MockField f) + { + var val = field.Value; + f.Revision = this; + SetFieldValue(field.FieldDefinition, val); + } + + + } + } + } + public MockWorkItem([NotNull] IWorkItemType type, int id) : this(type, new KeyValuePair(CoreFieldRefNames.Id, id)) { @@ -80,6 +108,11 @@ public MockWorkItem([NotNull] IWorkItemType type, [CanBeNull] Dictionary(); Revisions = new HashSet(); ApplyRules(); @@ -140,8 +173,8 @@ public string ReproSteps public new int Id { - get => base.Id; - set => this[CoreFieldRefNames.Id] = value; + get => GetValue(CoreFieldRefNames.Id); + set => SetValue(CoreFieldRefNames.Id, value); } public override bool IsDirty @@ -162,7 +195,20 @@ public override string Keywords public new int RelatedLinkCount => Links.OfType().Count(); - public override IEnumerable Revisions { get; } + IEnumerable IWorkItem.Revisions => Revisions; + + public new IEnumerable Revisions + { + get => _revisions; + set + { + _revisions = value; + if (_revisions != null) + { + SetFieldValue(Type.FieldDefinitions[CoreFieldRefNames.Rev], _revisions.Count() + 1); + } + } + } public override Uri Uri => new Uri($"vstfs:///WorkItemTracking/WorkItem/{Id}"); @@ -178,32 +224,60 @@ public override void Close() public override IWorkItem Copy() { - using (var stream = new MemoryStream()) + // Copy links + var target = new MockWorkItem(Type); + foreach (var definition in Type.FieldDefinitions) { - var formatter = new BinaryFormatter(); - formatter.Serialize(stream, this); - stream.Position = 0; + // Verify field is clonable + if (definition.IsCloneable()) + { + Fields.TryGetById(definition.Id, out IField field); + if (field != null && field.Value != null && !Equals(field.Value, string.Empty)) + { + var obj2 = field.Value; + target.SetFieldValue(definition, obj2); + } + } + } + target.History = $"Copied from Work Item {Id}"; - var newItem = (MockWorkItem)formatter.Deserialize(stream); + // Copy links + IEnumerator enumerator = Links.GetEnumerator(); + while (enumerator.MoveNext()) + { + //TODO: Clone link + } + + if (!IsNew) + { var s = Type.Store(); + IWorkItemLinkType e; if (s != null) { - var e = s.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related]; - newItem.Links.Add(newItem.CreateRelatedLink(e.ForwardEnd, this)); + e = s.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related]; } else { using (var wis = new MockWorkItemStore()) { - newItem.Links.Add(newItem.CreateRelatedLink(wis.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related].ForwardEnd, this)); + e = wis.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Related]; } } + // Our limitation: need an ID to link so save before we do anything else + target.Id = target._tempId; + target.Links.Add(target.CreateRelatedLink(e.ForwardEnd, this)); - newItem.Id = 0; - newItem.ApplyRules(); - return newItem; + // Recipricol links are typically handled in Save operation + Links.Add(CreateRelatedLink(e.ReverseEnd, target)); + + target.Id = 0; } + + + target.ApplyRules(); + return target; + } public override bool IsValid() @@ -218,6 +292,7 @@ public override void Open() public override void PartialOpen() { PartialOpenWasCalled = true; + ApplyRules(false); } public override void Reset() diff --git a/test/Qwiq.Mocks/MockWorkItemTypeCollection.cs b/test/Qwiq.Mocks/MockWorkItemTypeCollection.cs new file mode 100644 index 00000000..c4125dd1 --- /dev/null +++ b/test/Qwiq.Mocks/MockWorkItemTypeCollection.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +using JetBrains.Annotations; + +namespace Microsoft.Qwiq.Mocks +{ + internal class MockWorkItemTypeCollection : WorkItemTypeCollection + { + public MockWorkItemTypeCollection([NotNull] IWorkItemStore store) + : base((List)null) + { + ItemFactory = () => new[] + { + new MockWorkItemType(WorkItemTypeDefinitions.Task, null, store), + new MockWorkItemType(WorkItemTypeDefinitions.Deliverable, null, store), + new MockWorkItemType(WorkItemTypeDefinitions.Scenario, null, store), + new MockWorkItemType(WorkItemTypeDefinitions.CustomerPromise, null, store), + new MockWorkItemType(WorkItemTypeDefinitions.Bug, null, store), + new MockWorkItemType(WorkItemTypeDefinitions.Measure, null, store) + }; + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 5b96535e..d8887b00 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -54,6 +54,7 @@ + @@ -62,6 +63,7 @@ + From 98fa5d8e688f3928f4398408c49042312ca563da Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:09:01 -0700 Subject: [PATCH 225/251] Add JetBrains annotations --- src/Qwiq.Core/Field.cs | 41 ++++++++++--------- src/Qwiq.Core/FieldCollection.cs | 16 ++++++-- src/Qwiq.Core/IFieldDefinition.cs | 7 +++- src/Qwiq.Core/IReadOnlyField.cs | 10 +++++ src/Qwiq.Core/IReadOnlyObjectWithIdList.cs | 1 + src/Qwiq.Core/IRevision.cs | 13 +++--- src/Qwiq.Core/WorkItemTypeCollection.cs | 6 ++- .../IIdentityManagementService.cs | 18 ++++++-- 8 files changed, 78 insertions(+), 34 deletions(-) diff --git a/src/Qwiq.Core/Field.cs b/src/Qwiq.Core/Field.cs index b68c22dd..82828371 100644 --- a/src/Qwiq.Core/Field.cs +++ b/src/Qwiq.Core/Field.cs @@ -1,43 +1,46 @@ using System; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { internal class Field : IField { private readonly IRevisionInternal _revision; - private readonly IFieldDefinition _fieldDefinition; - - protected internal Field(IRevisionInternal revision, IFieldDefinition fieldDefinition) + protected internal Field([NotNull] IRevisionInternal revision, [NotNull] IFieldDefinition fieldDefinition) { _revision = revision ?? throw new ArgumentNullException(nameof(revision)); - _fieldDefinition = fieldDefinition ?? throw new ArgumentNullException(nameof(fieldDefinition)); + FieldDefinition = fieldDefinition ?? throw new ArgumentNullException(nameof(fieldDefinition)); } - public virtual bool IsValid => ValidationState == ValidationState.Valid; + /// + public IFieldDefinition FieldDefinition { get; } - public virtual string Name => _fieldDefinition.Name; + public virtual int Id => FieldDefinition.Id; - public virtual string ReferenceName => _fieldDefinition.ReferenceName; + public virtual bool IsChangedByUser => throw new NotImplementedException(); - public virtual object OriginalValue => throw new NotImplementedException(); + public virtual bool IsDirty => throw new NotImplementedException(); - public virtual ValidationState ValidationState => throw new NotImplementedException(); + public virtual bool IsEditable => throw new NotImplementedException(); - public virtual bool IsChangedByUser => throw new NotImplementedException(); + public virtual bool IsRequired => throw new NotImplementedException(); - public virtual object Value - { - get => _revision.GetCurrentFieldValue(_fieldDefinition); - set => _revision.SetFieldValue(_fieldDefinition, value); - } + public virtual bool IsValid => ValidationState == ValidationState.Valid; - public virtual int Id => _fieldDefinition.Id; + public virtual string Name => FieldDefinition.Name; - public virtual bool IsDirty => throw new NotImplementedException(); + public virtual object OriginalValue => throw new NotImplementedException(); - public virtual bool IsEditable => throw new NotImplementedException(); + public virtual string ReferenceName => FieldDefinition.ReferenceName; - public virtual bool IsRequired => throw new NotImplementedException(); + public virtual ValidationState ValidationState => throw new NotImplementedException(); + + public virtual object Value + { + get => _revision.GetCurrentFieldValue(FieldDefinition); + set => _revision.SetFieldValue(FieldDefinition, value); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/FieldCollection.cs b/src/Qwiq.Core/FieldCollection.cs index a6963042..ed3cec4f 100644 --- a/src/Qwiq.Core/FieldCollection.cs +++ b/src/Qwiq.Core/FieldCollection.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class FieldCollection : IFieldCollection @@ -16,9 +18,9 @@ public class FieldCollection : IFieldCollection private readonly IRevisionInternal _revision; internal FieldCollection( - IRevisionInternal revision, - IFieldDefinitionCollection definitions, - Func fieldFactory) + [NotNull] IRevisionInternal revision, + [NotNull] IFieldDefinitionCollection definitions, + [NotNull] Func fieldFactory) { _revision = revision; _definitions = definitions; @@ -140,5 +142,13 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + protected internal void SetField([NotNull] IField field) + { + if (field == null) throw new ArgumentNullException(nameof(field)); + if (!_definitions.Contains(field.ReferenceName)) throw new InvalidOperationException(); + + _cache[field.FieldDefinition.Id] = field; + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IFieldDefinition.cs b/src/Qwiq.Core/IFieldDefinition.cs index 24c67d2d..e9bbc51d 100644 --- a/src/Qwiq.Core/IFieldDefinition.cs +++ b/src/Qwiq.Core/IFieldDefinition.cs @@ -1,8 +1,13 @@ -namespace Microsoft.Qwiq +using JetBrains.Annotations; + +namespace Microsoft.Qwiq { public interface IFieldDefinition : IIdentifiable { + [NotNull] string Name { get; } + + [NotNull] string ReferenceName { get; } } } diff --git a/src/Qwiq.Core/IReadOnlyField.cs b/src/Qwiq.Core/IReadOnlyField.cs index 3d4a5838..062a6b70 100644 --- a/src/Qwiq.Core/IReadOnlyField.cs +++ b/src/Qwiq.Core/IReadOnlyField.cs @@ -1,9 +1,19 @@ +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public interface IReadOnlyField : IIdentifiable { + [NotNull] string Name { get; } + + [NotNull] string ReferenceName { get; } + + [CanBeNull] object Value { get; } + + [NotNull] + IFieldDefinition FieldDefinition { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs b/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs index 32e59c28..e11a5175 100644 --- a/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs +++ b/src/Qwiq.Core/IReadOnlyObjectWithIdList.cs @@ -24,6 +24,7 @@ public interface IReadOnlyObjectWithIdCollection : IReadOnlyObjectWithNa /// /// The identity of an element. /// The element with the specified in the read-only list. + [CanBeNull] T GetById([NotNull] TId id); /// diff --git a/src/Qwiq.Core/IRevision.cs b/src/Qwiq.Core/IRevision.cs index 248f4283..0f3a18d7 100644 --- a/src/Qwiq.Core/IRevision.cs +++ b/src/Qwiq.Core/IRevision.cs @@ -1,11 +1,14 @@ +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public interface IRevision : IWorkItemCore { - ///// - ///// Gets the fields of the work item in this revision. - ///// - //IFieldCollection Fields { get; } + /// + /// Gets the fields of the work item in this revision. + /// + [NotNull] + IFieldCollection Fields { get; } ///// ///// Gets the value of the specified field in the work item of this revision. @@ -14,7 +17,7 @@ public interface IRevision : IWorkItemCore ///// The value of the specified field. //new object this[string name] { get; } - + } } diff --git a/src/Qwiq.Core/WorkItemTypeCollection.cs b/src/Qwiq.Core/WorkItemTypeCollection.cs index 0ef9ff54..62198642 100644 --- a/src/Qwiq.Core/WorkItemTypeCollection.cs +++ b/src/Qwiq.Core/WorkItemTypeCollection.cs @@ -2,18 +2,20 @@ using System.Collections.Generic; using System.Diagnostics; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { public class WorkItemTypeCollection : ReadOnlyObjectWithNameCollection, IWorkItemTypeCollection { [DebuggerStepThrough] - internal WorkItemTypeCollection(Func> workItemTypesFactory) + internal WorkItemTypeCollection([NotNull] Func> workItemTypesFactory) : base(workItemTypesFactory, type => type.Name) { } [DebuggerStepThrough] - internal WorkItemTypeCollection(List workItemTypes) + internal WorkItemTypeCollection([CanBeNull] List workItemTypes) : base(workItemTypes, type => type.Name) { } diff --git a/src/Qwiq.Identity/IIdentityManagementService.cs b/src/Qwiq.Identity/IIdentityManagementService.cs index 23280ee4..6d2d46d0 100644 --- a/src/Qwiq.Identity/IIdentityManagementService.cs +++ b/src/Qwiq.Identity/IIdentityManagementService.cs @@ -1,17 +1,23 @@ using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq.Identity { public interface IIdentityManagementService { - IIdentityDescriptor CreateIdentityDescriptor(string identityType, string identifier); + [NotNull] + [Pure] + IIdentityDescriptor CreateIdentityDescriptor([NotNull] string identityType, [NotNull] string identifier); /// /// Read identities for given . /// /// A set of s /// - IEnumerable ReadIdentities(IEnumerable descriptors); + [NotNull] + [Pure] + IEnumerable ReadIdentities([NotNull] IEnumerable descriptors); /// /// Read identities for given and . @@ -19,10 +25,14 @@ public interface IIdentityManagementService /// Specific search. /// Actual search strings. /// An enumerable set of identities corresponding 1 to 1 with . + [NotNull] + [Pure] IEnumerable>> ReadIdentities( IdentitySearchFactor searchFactor, - IEnumerable searchFactorValues); + [NotNull] IEnumerable searchFactorValues); - ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, string searchFactorValue); + [CanBeNull] + [Pure] + ITeamFoundationIdentity ReadIdentity(IdentitySearchFactor searchFactor, [NotNull] string searchFactorValue); } } \ No newline at end of file From adffae46c3b7a5372a7110b65f9f59602aa66115 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:12:55 -0700 Subject: [PATCH 226/251] Code clean up --- .../ExceptionHandlingDynamicProxy.cs | 8 +- src/Qwiq.Core/Exceptions/ExceptionMapper.cs | 10 +- src/Qwiq.Core/Extensions.cs | 82 ++++++++++++++ src/Qwiq.Core/FieldDefinition.cs | 16 ++- src/Qwiq.Core/FieldDefinitionComparer.cs | 9 +- src/Qwiq.Core/IFieldDefinition.Extensions.cs | 62 +++++++++++ src/Qwiq.Core/IWorkItem.cs | 60 +++++----- src/Qwiq.Core/Properties/AssemblyInfo.cs | 1 + src/Qwiq.Core/Qwiq.Core.csproj | 2 + src/Qwiq.Core/WorkItem.cs | 63 +++++------ src/Qwiq.Core/WorkItemCore.cs | 86 +++++++------- test/Qwiq.Tests.Common/Extensions.cs | 105 ------------------ .../Qwiq.Tests.Common.csproj | 4 +- 13 files changed, 269 insertions(+), 239 deletions(-) create mode 100644 src/Qwiq.Core/Extensions.cs create mode 100644 src/Qwiq.Core/IFieldDefinition.Extensions.cs delete mode 100644 test/Qwiq.Tests.Common/Extensions.cs diff --git a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs index cd761be8..2b4d6535 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionHandlingDynamicProxy.cs @@ -1,12 +1,10 @@ +using Castle.DynamicProxy; +using JetBrains.Annotations; using System; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.ExceptionServices; -using Castle.DynamicProxy; - -using JetBrains.Annotations; - namespace Microsoft.Qwiq.Exceptions { [DebuggerStepThrough] @@ -22,7 +20,7 @@ public ExceptionHandlingDynamicProxy([NotNull] IExceptionMapper exceptionMapper) _exceptionMapper = exceptionMapper; } - public void Intercept([NotNull] IInvocation invocation) + public void Intercept(IInvocation invocation) { try { diff --git a/src/Qwiq.Core/Exceptions/ExceptionMapper.cs b/src/Qwiq.Core/Exceptions/ExceptionMapper.cs index d7af6614..1ce77714 100644 --- a/src/Qwiq.Core/Exceptions/ExceptionMapper.cs +++ b/src/Qwiq.Core/Exceptions/ExceptionMapper.cs @@ -1,9 +1,8 @@ +using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; -using JetBrains.Annotations; - namespace Microsoft.Qwiq.Exceptions { internal class ExceptionMapper : IExceptionMapper @@ -22,11 +21,12 @@ public ExceptionMapper( _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); } - public Exception Map([CanBeNull] Exception ex) + public Exception Map(Exception ex) { return MapImpl(ex) ?? ex; } + [CanBeNull] private Exception MapImpl(Exception ex) { var q = new Queue(); @@ -54,7 +54,6 @@ private Exception MapImpl(Exception ex) if (exceptions == null) continue; - for (var j = 0; j < exceptions.Count; j++) { var childException = exceptions[j]; @@ -71,5 +70,4 @@ private Exception MapImpl(Exception ex) return null; } } -} - +} \ No newline at end of file diff --git a/src/Qwiq.Core/Extensions.cs b/src/Qwiq.Core/Extensions.cs new file mode 100644 index 00000000..d2dd2078 --- /dev/null +++ b/src/Qwiq.Core/Extensions.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using JetBrains.Annotations; + +namespace Microsoft.Qwiq +{ + public static partial class Extensions + { + [NotNull] + private static readonly string[] Split = { "\r\n", "\n" }; + + internal static string EachToUsefulString(this IEnumerable enumerable, int limit = 10) + { + var sb = new StringBuilder(); + sb.AppendLine("{"); + sb.Append(string.Join(",\n", enumerable.Select(x => ToUsefulString(x).Tab()).Take(limit).ToArray())); + if (enumerable.Count() > limit) + if (enumerable.Count() > limit + 1) sb.AppendLine($",\n ...({enumerable.Count() - limit} more elements)"); + else sb.AppendLine(",\n" + enumerable.Last().ToUsefulString().Tab()); + else sb.AppendLine(); + + sb.AppendLine("}"); + + return sb.ToString(); + } + + internal static string ToUsefulString([CanBeNull] this object obj) + { + string str; + if (obj == null) return "[null]"; + + if (obj.GetType() == typeof(string)) + { + str = (string)obj; + return "\"" + str.Replace("\n", "\\n").Replace("\r", "\\r") + "\""; + } + + if (obj.GetType().IsValueType) return "[" + obj + "]"; + + if (obj is IEnumerable enumerable) + { + var e = enumerable.Cast(); + + return enumerable.GetType() + ":\n" + e.EachToUsefulString(); + } + + str = obj.ToString(); + + if (string.IsNullOrEmpty(str)) return $"{obj.GetType()}:[]"; + + str = str.Trim(); + + if (str.Contains("\n")) return string.Format("{1}:\r\n[\r\n{0}\r\n]", str.Tab(), obj.GetType()); + + return obj.GetType().ToString() == str ? obj.GetType().ToString() : $"{obj.GetType()}:[{str}]"; + } + + [NotNull] + private static string Tab([CanBeNull] this string str) + { + if (string.IsNullOrEmpty(str)) return string.Empty; + + var split = str.Split(Split, StringSplitOptions.None); + var sb = new StringBuilder(); + + sb.Append(" "); + sb.Append(split[0]); + foreach (var part in split.Skip(1)) + { + sb.AppendLine(); + sb.Append(" "); + sb.Append(part); + } + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/FieldDefinition.cs b/src/Qwiq.Core/FieldDefinition.cs index 4a3c94a6..5a6796d2 100644 --- a/src/Qwiq.Core/FieldDefinition.cs +++ b/src/Qwiq.Core/FieldDefinition.cs @@ -18,8 +18,7 @@ internal FieldDefinition(int id, [NotNull] string referenceName, [NotNull] strin if (string.IsNullOrWhiteSpace(referenceName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); Name = string.Intern(name); ReferenceName = string.Intern(referenceName); @@ -44,8 +43,7 @@ internal FieldDefinition([NotNull] string referenceName, [NotNull] string name) if (string.IsNullOrWhiteSpace(referenceName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(referenceName)); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); Name = string.Intern(name); ReferenceName = string.Intern(referenceName); @@ -56,17 +54,17 @@ internal FieldDefinition([NotNull] string referenceName, [NotNull] string name) Id = id; } - public bool Equals(IFieldDefinition other) - { - return FieldDefinitionComparer.Default.Equals(this, other); - } - public int Id { get; } public string Name { get; } public string ReferenceName { get; } + public bool Equals(IFieldDefinition other) + { + return FieldDefinitionComparer.Default.Equals(this, other); + } + public override bool Equals(object obj) { return FieldDefinitionComparer.Default.Equals(this, obj as IFieldDefinition); diff --git a/src/Qwiq.Core/FieldDefinitionComparer.cs b/src/Qwiq.Core/FieldDefinitionComparer.cs index b1d54750..d202804b 100644 --- a/src/Qwiq.Core/FieldDefinitionComparer.cs +++ b/src/Qwiq.Core/FieldDefinitionComparer.cs @@ -1,6 +1,5 @@ -using System; - using JetBrains.Annotations; +using System; namespace Microsoft.Qwiq { @@ -8,13 +7,12 @@ internal class FieldDefinitionComparer : GenericComparer { private FieldDefinitionComparer() { - } [NotNull] internal new static FieldDefinitionComparer Default => Nested.Instance; - public override bool Equals([CanBeNull] IFieldDefinition x, [CanBeNull] IFieldDefinition y) + public override bool Equals(IFieldDefinition x, IFieldDefinition y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; @@ -50,8 +48,7 @@ private class Nested // ReSharper restore MemberHidesStaticFromOuterClass - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit + // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static Nested() { diff --git a/src/Qwiq.Core/IFieldDefinition.Extensions.cs b/src/Qwiq.Core/IFieldDefinition.Extensions.cs new file mode 100644 index 00000000..52f14d35 --- /dev/null +++ b/src/Qwiq.Core/IFieldDefinition.Extensions.cs @@ -0,0 +1,62 @@ +using JetBrains.Annotations; + +namespace Microsoft.Qwiq +{ + public static partial class Extensions + { + public static bool IsCloneable([NotNull] this IFieldDefinition definition) + { + switch (definition.Id) + { + case (int)CoreField.AreaPath: + case (int)CoreField.RevisedDate: + case (int)CoreField.ChangedDate: + case (int)CoreField.State: + case (int)CoreField.AuthorizedDate: + case (int)CoreField.Watermark: + case (int)CoreField.Rev: + case (int)CoreField.ChangedBy: + case (int)CoreField.Reason: + case (int)CoreField.IterationPath: + case (int)CoreField.CreatedBy: + case (int)CoreField.History: + case (int)CoreField.WorkItemType: + case (int)CoreField.CreatedDate: + return false; + } + + return definition.IsEditable(); + } + + public static bool IsEditable([NotNull] this IFieldDefinition definition) + { + switch (definition.Id) + { + case (int)CoreField.IterationPath: + case (int)CoreField.AreaPath: + return true; + + case (int)CoreField.Id: + case (int)CoreField.Rev: + case (int)CoreField.WorkItemType: + return false; + } + + return !definition.IsComputed(); + } + + public static bool IsComputed([NotNull] this IFieldDefinition definition) + { + switch (definition.Id) + { + case (int)CoreField.AreaPath: + case (int)CoreField.NodeName: + case (int)CoreField.TeamProject: + case (int)CoreField.IterationPath: + return true; + } + + return false; + } + } +} diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index 84ef1863..dfdd26c8 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -4,8 +4,7 @@ namespace Microsoft.Qwiq { /// - /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference - /// all the TFS libraries. + /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference all the TFS libraries. /// public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable { @@ -19,12 +18,10 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable new int ExternalLinkCount { get; } - IFieldCollection Fields { get; } - new int HyperlinkCount { get; } /// - /// Gets the ID of this work item. + /// Gets the ID of this work item. /// new int Id { get; } @@ -33,7 +30,7 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable string Keywords { get; set; } /// - /// Gets the links of the work item in this revision. + /// Gets the links of the work item in this revision. /// ICollection Links { get; } @@ -44,51 +41,50 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable new DateTime RevisedDate { get; } /// - /// Gets the integer that represents the revision number of this work item. + /// Gets the integer that represents the revision number of this work item. /// /// int Revision { get; } /// - /// Gets an object that represents a collection of valid revision numbers for this work - /// item. + /// Gets an object that represents a collection of valid revision numbers for this work item. /// IEnumerable Revisions { get; } /// - /// Gets a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType object - /// that represents the type of this work item. + /// Gets a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType object that represents the type of this work item. /// /// - /// The Type property is null. + /// The Type property is null. /// IWorkItemType Type { get; } /// - /// Gets the uniform resource identifier (System.Uri) of this work item. + /// Gets the uniform resource identifier (System.Uri) of this work item. /// [Obsolete("This property is deprecated and will be removed in a future release. See IWorkItemReference.Url instead.")] Uri Uri { get; } /// - /// Applies the server rules for validation and fix up to the work item. + /// Applies the server rules for validation and fix up to the work item. /// /// - /// If true, will set ChangedBy to the user context of the . - /// If false, ChangedBy will not be modified. + /// If true, will set ChangedBy to the user context of the . If false, ChangedBy will not be modified. /// /// - /// Use ApplyRules(true) in the case where you want "transparent fix ups". + /// Use ApplyRules(true) in the case where you want "transparent fix ups". /// void ApplyRules(bool doNotUpdateChangedBy = false); /// - /// Closes this WorkItem instance and frees memory that is associated with it. + /// Closes this WorkItem instance and frees memory that is associated with it. /// void Close(); /// - /// Creates a copy of this WorkItem instance. + /// Creates a copy of this WorkItem instance. /// - /// A new WorkItem instance that is a copy of this WorkItem instance. + /// + /// A new WorkItem instance that is a copy of this WorkItem instance. + /// IWorkItem Copy(); IHyperlink CreateHyperlink(string location); @@ -99,49 +95,47 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable IRelatedLink CreateRelatedLink(int relatedWorkItemId, IWorkItemLinkTypeEnd linkTypeEnd = null); /// - /// Validates the fields of this work item. + /// Validates the fields of this work item. /// /// - /// True if all fields are valid. False if at least one field is not valid. + /// True if all fields are valid. False if at least one field is not valid. /// bool IsValid(); /// - /// Opens this work item for modification. + /// Opens this work item for modification. /// void Open(); /// - /// Opens this work item for modification when transmitting minimal amounts of data over the network. + /// Opens this work item for modification when transmitting minimal amounts of data over the network. /// - /// This WorkItem instance does not belong to a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemCollection. - /// This WorkItem instance could not be opened for edit correctly. + /// This WorkItem instance does not belong to a Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemCollection. This WorkItem instance could not be opened for edit correctly. void PartialOpen(); /// - /// Reverts all changes that were made since the last save. + /// Reverts all changes that were made since the last save. /// void Reset(); /// - /// Saves any pending changes on this work item. + /// Saves any pending changes on this work item. /// void Save(); /// - /// Saves any pending changes on this work item. + /// Saves any pending changes on this work item. /// /// - /// If set to , does not return errors if the link that - /// is being added already exists or the link that is being removed was already removed. + /// If set to , does not return errors if the link that is being added already exists or the link that is being removed was already removed. /// void Save(SaveFlags saveFlags); /// - /// Gets an ArrayList of fields in this work item that are not valid. + /// Gets an ArrayList of fields in this work item that are not valid. /// /// - /// An ArrayList of the fields in this work item that are not valid. + /// An ArrayList of the fields in this work item that are not valid. /// IEnumerable Validate(); } diff --git a/src/Qwiq.Core/Properties/AssemblyInfo.cs b/src/Qwiq.Core/Properties/AssemblyInfo.cs index ffd308bd..e3466abb 100644 --- a/src/Qwiq.Core/Properties/AssemblyInfo.cs +++ b/src/Qwiq.Core/Properties/AssemblyInfo.cs @@ -22,3 +22,4 @@ [assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.Soap")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Mapper")] [assembly: InternalsVisibleTo("Microsoft.Qwiq.Mapper.Identity")] +[assembly: InternalsVisibleTo("Microsoft.Qwiq.Identity.UnitTests")] diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index bc08bdef..d47a7d45 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -99,6 +99,7 @@ + @@ -123,6 +124,7 @@ + diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index 7887bff6..b6c1014e 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -10,21 +10,22 @@ namespace Microsoft.Qwiq /// A compatability class /// /// + /// /// /// public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable { [CanBeNull] - private readonly IWorkItemType _type; - - private bool _useFields = true; + private readonly Lazy _lazyType; - private IFieldCollection _fields; + [CanBeNull] + private readonly IWorkItemType _type; [CanBeNull] private Func _fieldFactory; - [CanBeNull] - private readonly Lazy _lazyType; + private IFieldCollection _fields; + + private bool _useFields = true; protected internal WorkItem([NotNull] IWorkItemType type, [CanBeNull] Dictionary fields) : base(fields) @@ -39,7 +40,6 @@ protected internal WorkItem([NotNull] IWorkItemType type) Contract.Requires(type != null); _type = type ?? throw new ArgumentNullException(nameof(type)); - } protected internal WorkItem([NotNull] Lazy type) @@ -50,20 +50,12 @@ protected internal WorkItem([NotNull] Lazy type) protected internal WorkItem([NotNull] IWorkItemType type, [NotNull] Func fieldCollectionFactory) { - Contract.Requires(type != null); Contract.Requires(fieldCollectionFactory != null); _type = type ?? throw new ArgumentNullException(nameof(type)); _fieldFactory = fieldCollectionFactory ?? throw new ArgumentNullException(nameof(fieldCollectionFactory)); } - public virtual int Revision => Rev; - - public bool Equals(IWorkItem other) - { - return WorkItemComparer.Default.Equals(this, other); - } - public new virtual int AttachedFileCount => base.AttachedFileCount.GetValueOrDefault(0); public virtual IEnumerable Attachments => throw new NotSupportedException(); @@ -87,14 +79,10 @@ public virtual IFieldCollection Fields } else { - _fields = new FieldCollection( - this, - Type.FieldDefinitions, - (revision, definition) => new Field(revision, definition)); + _fields = new FieldCollection(this, Type.FieldDefinitions, (revision, definition) => new Field(revision, definition)); } return _fields; - } } @@ -118,6 +106,8 @@ public virtual string Keywords public new virtual DateTime RevisedDate => base.RevisedDate.GetValueOrDefault(DateTime.MinValue); + public virtual int Revision => Rev; + public virtual IEnumerable Revisions => throw new NotSupportedException(); public virtual IWorkItemType Type => _type ?? _lazyType?.Value ?? throw new NotSupportedException(); @@ -187,49 +177,49 @@ public virtual IRelatedLink CreateRelatedLink(int relatedWorkItemId, IWorkItemLi throw new NotSupportedException(); } - public virtual bool IsValid() + public bool Equals(IWorkItem other) { - throw new NotSupportedException(); + return WorkItemComparer.Default.Equals(this, other); } - public virtual void Open() + public override bool Equals(object obj) { - throw new NotSupportedException(); + return WorkItemComparer.Default.Equals(this, obj as IWorkItem); } - public virtual void PartialOpen() + public override int GetHashCode() { - throw new NotSupportedException(); + return WorkItemComparer.Default.GetHashCode(this); } - public virtual void Reset() + public virtual bool IsValid() { throw new NotSupportedException(); } - public virtual void Save() + public virtual void Open() { throw new NotSupportedException(); } - public virtual void Save(SaveFlags saveFlags) + public virtual void PartialOpen() { throw new NotSupportedException(); } - public virtual IEnumerable Validate() + public virtual void Reset() { throw new NotSupportedException(); } - public override bool Equals(object obj) + public virtual void Save() { - return WorkItemComparer.Default.Equals(this, obj as IWorkItem); + throw new NotSupportedException(); } - public override int GetHashCode() + public virtual void Save(SaveFlags saveFlags) { - return WorkItemComparer.Default.GetHashCode(this); + throw new NotSupportedException(); } /// @@ -237,5 +227,10 @@ public override string ToString() { return $"{WorkItemType} {Id} {Title}"; } + + public virtual IEnumerable Validate() + { + throw new NotSupportedException(); + } } } \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemCore.cs b/src/Qwiq.Core/WorkItemCore.cs index d0c148a6..226e809e 100644 --- a/src/Qwiq.Core/WorkItemCore.cs +++ b/src/Qwiq.Core/WorkItemCore.cs @@ -1,9 +1,9 @@ +using JetBrains.Annotations; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; -using JetBrains.Annotations; - namespace Microsoft.Qwiq { public abstract class WorkItemCore : IWorkItemCore, IEquatable, IRevisionInternal @@ -20,20 +20,22 @@ protected internal WorkItemCore([CanBeNull] Dictionary fields) _fields = fields ?? new Dictionary(StringComparer.OrdinalIgnoreCase); } + public abstract string Url { get; } public virtual int? Id => GetValue(CoreFieldRefNames.Id); public virtual int? Rev => GetValue(CoreFieldRefNames.Rev); - public abstract string Url { get; } - /// /// Gets or sets the with the specified name. /// /// /// The . /// - /// The name. - /// + /// + /// The name. + /// + /// + /// /// /// name is null /// @@ -52,19 +54,40 @@ public virtual object this[string name] } } + public bool Equals(IWorkItemCore other) + { + return NullableIdentifiableComparer.Default.Equals(this, other); + } + + public override bool Equals(object obj) + { + return NullableIdentifiableComparer.Default.Equals(this, obj as IWorkItemCore); + } + + public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) + { + if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); + return GetValue(fieldDefinition.ReferenceName); + } + + public override int GetHashCode() + { + return NullableIdentifiableComparer.Default.GetHashCode(this); + } + + public void SetFieldValue(IFieldDefinition fieldDefinition, object value) + { + if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); + SetValue(fieldDefinition.ReferenceName, value); + } + [JetBrains.Annotations.Pure] [CanBeNull] protected virtual T GetValue([NotNull] string name) { var value = GetValue(name); - if (value == null) - { - if (typeof(T) == typeof(string)) - { - return (T)(object)string.Empty; - } - } + if (value == null) if (typeof(T) == typeof(string)) return (T)(object)string.Empty; return TypeParser.Default.Parse(value, default(T)); } @@ -75,40 +98,23 @@ protected virtual object GetValue([NotNull] string name) { Contract.Requires(!string.IsNullOrEmpty(name)); if (_fields == null) throw new InvalidOperationException("Type must be initialized with fields."); - return _fields.TryGetValue(name, out object val) ? val : null; + _fields.TryGetValue(name, out object val); + +#if DEBUG + Trace.WriteLine($"Get \'{name}\': {val.ToUsefulString()}"); +#endif + + return val; } protected virtual void SetValue([NotNull] string name, [CanBeNull] object value) { if (_fields == null) throw new InvalidOperationException("Type must be initialized with fields."); _fields[name] = value; - } - - public bool Equals(IWorkItemCore other) - { - return NullableIdentifiableComparer.Default.Equals(this, other); - } - - public override bool Equals(object obj) - { - return NullableIdentifiableComparer.Default.Equals(this, obj as IWorkItemCore); - } - public override int GetHashCode() - { - return NullableIdentifiableComparer.Default.GetHashCode(this); - } - - public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) - { - if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); - return GetValue(fieldDefinition.ReferenceName); - } - - public void SetFieldValue(IFieldDefinition fieldDefinition, object value) - { - if (fieldDefinition == null) throw new ArgumentNullException(nameof(fieldDefinition)); - SetValue(fieldDefinition.ReferenceName, value); +#if DEBUG + Trace.WriteLine($"Set \'{name}\' to {value.ToUsefulString()}"); +#endif } } } \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/Extensions.cs b/test/Qwiq.Tests.Common/Extensions.cs deleted file mode 100644 index 139cae07..00000000 --- a/test/Qwiq.Tests.Common/Extensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Microsoft.Qwiq -{ - public static class Extensions - { - public static string EachToUsefulString(this IEnumerable enumerable, int limit = 10) - { - var sb = new StringBuilder(); - sb.AppendLine("{"); - sb.Append(string.Join(",\n", enumerable.Select(x => ToUsefulString(x).Tab()).Take(limit).ToArray())); - if (enumerable.Count() > limit) - { - if (enumerable.Count() > (limit+1)) - { - sb.AppendLine($",\n ...({enumerable.Count() - limit} more elements)"); - } - else - { - sb.AppendLine(",\n" + enumerable.Last().ToUsefulString().Tab()); - } - } - else - { - sb.AppendLine(); - } - - sb.AppendLine("}"); - - return sb.ToString(); - } - - public static string ToUsefulString(this object obj) - { - string str; - if (obj == null) - { - return "[null]"; - } - - if (obj.GetType() == typeof(string)) - { - str = (string)obj; - return "\"" + str.Replace("\n", "\\n").Replace("\r", "\\r") + "\""; - } - - if (obj.GetType().IsValueType) - { - return "[" + obj + "]"; - } - - if (obj is IEnumerable) - { - var enumerable = ((IEnumerable)obj).Cast(); - - return obj.GetType() + ":\n" + enumerable.EachToUsefulString(); - } - - str = obj.ToString(); - - if (string.IsNullOrEmpty(str)) - { - return $"{obj.GetType()}:[]"; - } - - str = str.Trim(); - - if (str.Contains("\n")) - { - return string.Format("{1}:\r\n[\r\n{0}\r\n]", str.Tab(), obj.GetType()); - } - - if (obj.GetType().ToString() == str) - { - return obj.GetType().ToString(); - } - - return $"{obj.GetType()}:[{str}]"; - } - - private static string Tab(this string str) - { - if (string.IsNullOrEmpty(str)) - { - return string.Empty; - } - - var split = str.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); - var sb = new StringBuilder(); - - sb.Append(" " + split[0]); - foreach (var part in split.Skip(1)) - { - sb.AppendLine(); - sb.Append(" " + part); - } - - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index bd2624e4..b27f6b50 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -35,6 +35,9 @@ Properties\AssemblyInfo.Common.cs + + Extensions.cs + GenericComparer.cs @@ -43,7 +46,6 @@ - From c77b6f0b89d83e838ec83049939383e2acfc8fd2 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 5 May 2017 15:13:32 -0700 Subject: [PATCH 227/251] Implementation for Revision Used mostly in Mocks for WebJobs project --- src/Qwiq.Core/Revision.cs | 45 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Qwiq.Core/Revision.cs b/src/Qwiq.Core/Revision.cs index 5757fb0b..224e3d28 100644 --- a/src/Qwiq.Core/Revision.cs +++ b/src/Qwiq.Core/Revision.cs @@ -1,30 +1,35 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { - internal class Revision : IRevision + public class Revision : IRevision, IRevisionInternal { - private readonly Lazy _fields; + internal IFieldDefinitionCollection FieldDefinitions { get; } + + private Dictionary _values; + + private IFieldCollection _fields; internal Revision( - IFieldDefinitionCollection definitions, - int revision, - Func fieldFactory) + [NotNull] IFieldDefinitionCollection definitions, + int revision) { Rev = revision; - new Dictionary(); - _fields = new Lazy(() => fieldFactory(this, definitions)); + _values = new Dictionary(); + FieldDefinitions = definitions; } - internal Revision(WorkItem workItem, int revision) + internal Revision([NotNull] IWorkItem workItem, int revision) { WorkItem = workItem ?? throw new ArgumentNullException(nameof(workItem)); Rev = revision; - _fields = new Lazy(() => WorkItem.Fields); + FieldDefinitions = workItem.Type.FieldDefinitions; } - public IFieldCollection Fields => _fields.Value; + public IFieldCollection Fields => _fields ?? (_fields = new FieldCollection(this, FieldDefinitions, (r, d) => new Field(r, d))); public int? Id => WorkItem?.Id; @@ -32,7 +37,7 @@ internal Revision(WorkItem workItem, int revision) public string Url => WorkItem?.Url; - private WorkItem WorkItem { get; } + private IWorkItem WorkItem { get; } public virtual object this[string name] { @@ -48,5 +53,23 @@ object IWorkItemCore.this[string name] get => this[name]; set => throw new NotSupportedException(); } + + /// + public object GetCurrentFieldValue(IFieldDefinition fieldDefinition) + { + if (WorkItem != null) return WorkItem.Fields[fieldDefinition.ReferenceName]; + + return _values[fieldDefinition.Id]; + } + + /// + void IRevisionInternal.SetFieldValue(IFieldDefinition fieldDefinition, object value) + { + throw new InvalidOperationException(); + } + + internal bool HasValue(int fieldId) => _values.ContainsKey(fieldId); + + internal void SetFieldValue(int fieldId, object value) => _values[fieldId] = value; } } \ No newline at end of file From def29745fe9a6543b8eb0dd858b3db9263b6ee85 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 08:15:03 -0700 Subject: [PATCH 228/251] Code clean up Move and rename files to match project conventions --- test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj | 4 ++-- .../WorkItemStore/{ => WorkItem}/WorkItemTests.cs | 2 +- ...ItemStoreTests.cs => WorkItemStoreContextSpecification.cs} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename test/Qwiq.Integration.Tests/WorkItemStore/{ => WorkItem}/WorkItemTests.cs (96%) rename test/Qwiq.Integration.Tests/WorkItemStore/{WorkItemStoreTests.cs => WorkItemStoreContextSpecification.cs} (83%) diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 070a82fa..c25dc90f 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -222,8 +222,8 @@ - - + + diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/WorkItemTests.cs similarity index 96% rename from test/Qwiq.Integration.Tests/WorkItemStore/WorkItemTests.cs rename to test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/WorkItemTests.cs index d81bf8ec..c6a7f256 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/WorkItemTests.cs @@ -6,7 +6,7 @@ namespace Microsoft.Qwiq.WorkItemStore { - public abstract class WorkItemContextSpecification : WorkItemStoreTests + public abstract class WorkItemContextSpecification : WorkItemStoreContextSpecification where T : IWorkItemStore { private const int Id = 10726528; diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreContextSpecification.cs similarity index 83% rename from test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreTests.cs rename to test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreContextSpecification.cs index 357cdc3d..b6d22317 100644 --- a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreTests.cs +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItemStoreContextSpecification.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.WorkItemStore { - public abstract class WorkItemStoreTests : TimedContextSpecification + public abstract class WorkItemStoreContextSpecification : TimedContextSpecification where T : IWorkItemStore { internal IQueryFactory QueryFactory; From 998bf0a9acdb0f82dc918ea77e90a47b1b2e3e99 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 08:51:30 -0700 Subject: [PATCH 229/251] Correct issue with Revision SOAP client had its own interface for revisions, causing the property `Revisions` to not be overriden on the `WorkItem` base class. Since the property is not implemented by default, the WebJobs integration project failed. The interface has been corrected and a new SOAP integration test added to verify basic functionality of the scenario. --- src/Qwiq.Core.Soap/IRevision.cs | 45 ------------------- src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj | 1 - src/Qwiq.Core.Soap/Revision.cs | 21 ++++++++- src/Qwiq.Core.Soap/WorkItem.cs | 2 +- src/Qwiq.Core/IRevision.cs | 41 ++++++++++++++--- src/Qwiq.Core/IWorkItem.cs | 7 ++- src/Qwiq.Core/Revision.cs | 19 +++++++- src/Qwiq.Core/WorkItem.cs | 2 +- .../Qwiq.IntegrationTests.csproj | 1 + .../WorkItem/Soap/RevisionTests.cs | 36 +++++++++++++++ 10 files changed, 117 insertions(+), 58 deletions(-) delete mode 100644 src/Qwiq.Core.Soap/IRevision.cs create mode 100644 test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/Soap/RevisionTests.cs diff --git a/src/Qwiq.Core.Soap/IRevision.cs b/src/Qwiq.Core.Soap/IRevision.cs deleted file mode 100644 index 3a826d05..00000000 --- a/src/Qwiq.Core.Soap/IRevision.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.Qwiq.Client.Soap -{ - public interface IRevision - { - /// - /// Gets the attachments of the work item in this revision. - /// - IEnumerable Attachments { get; } - - /// - /// Gets the fields of the work item in this revision. - /// - IFieldCollection Fields { get; } - - /// - /// Gets the index of this revision. - /// - int Index { get; } - - /// - /// Gets the links of the work item in this revision. - /// - IEnumerable Links { get; } - - /// - /// Gets the work item that is stored in this revision. - /// - IWorkItem WorkItem { get; } - - /// - /// Gets the value of the specified field in the work item of this revision. - /// - /// The field of interest in the work item of this revision. - /// The value of the specified field. - object this[string name] { get; } - - /// - /// Gets the tagline for this revision. - /// - /// Returns System.String. - string GetTagLine(); - } -} diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index 25ea5454..e54a4ac2 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -184,7 +184,6 @@ - diff --git a/src/Qwiq.Core.Soap/Revision.cs b/src/Qwiq.Core.Soap/Revision.cs index 5ece17be..2f4e14c6 100644 --- a/src/Qwiq.Core.Soap/Revision.cs +++ b/src/Qwiq.Core.Soap/Revision.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; + using Microsoft.Qwiq.Exceptions; using Tfs = Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -14,9 +16,10 @@ namespace Microsoft.Qwiq.Client.Soap /// internal class Revision : IRevision { + [NotNull] private readonly Tfs.Revision _rev; - internal Revision(Tfs.Revision revision) + internal Revision([NotNull] Tfs.Revision revision) { _rev = revision ?? throw new ArgumentNullException(nameof(revision)); } @@ -62,6 +65,16 @@ public IEnumerable Links public IWorkItem WorkItem => ExceptionHandlingDynamicProxyFactory .Create(new WorkItem(_rev.WorkItem)); + /// + public int? Rev => Index; + + /// + object IWorkItemCore.this[string name] + { + get =>_rev[name]; + set => throw new NotSupportedException(); + } + /// /// Gets the value of the specified field in the work item of this revision. /// @@ -77,5 +90,11 @@ public string GetTagLine() { return _rev.GetTagLine(); } + + /// + public int? Id => _rev.WorkItem.Id; + + /// + public string Url => throw new NotSupportedException(); } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/WorkItem.cs b/src/Qwiq.Core.Soap/WorkItem.cs index a445968f..a6077830 100644 --- a/src/Qwiq.Core.Soap/WorkItem.cs +++ b/src/Qwiq.Core.Soap/WorkItem.cs @@ -157,7 +157,7 @@ public override string Keywords /// Gets an object that represents a collection of valid revision numbers for this work /// item. /// - public new IEnumerable Revisions + public override IEnumerable Revisions { get { diff --git a/src/Qwiq.Core/IRevision.cs b/src/Qwiq.Core/IRevision.cs index 0f3a18d7..3e6882cd 100644 --- a/src/Qwiq.Core/IRevision.cs +++ b/src/Qwiq.Core/IRevision.cs @@ -1,22 +1,51 @@ +using System.Collections.Generic; + using JetBrains.Annotations; namespace Microsoft.Qwiq { public interface IRevision : IWorkItemCore { + /// + /// Gets the attachments of the work item in this revision. + /// + IEnumerable Attachments { get; } + /// /// Gets the fields of the work item in this revision. /// [NotNull] IFieldCollection Fields { get; } - ///// - ///// Gets the value of the specified field in the work item of this revision. - ///// - ///// The field of interest in the work item of this revision. - ///// The value of the specified field. - //new object this[string name] { get; } + /// + /// Gets the index of this revision. + /// + int Index { get; } + + /// + /// Gets the links of the work item in this revision. + /// + IEnumerable Links { get; } + + /// + /// Gets the work item that is stored in this revision. + /// + [NotNull] + IWorkItem WorkItem { get; } + /// + /// Gets the value of the specified field in the work item of this revision. + /// + /// The field of interest in the work item of this revision. + /// The value of the specified field. + [CanBeNull] + new object this[[NotNull] string name] { get; } + + /// + /// Gets the tagline for this revision. + /// + /// Returns System.String. + string GetTagLine(); } } diff --git a/src/Qwiq.Core/IWorkItem.cs b/src/Qwiq.Core/IWorkItem.cs index dfdd26c8..fc4676c6 100644 --- a/src/Qwiq.Core/IWorkItem.cs +++ b/src/Qwiq.Core/IWorkItem.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; + namespace Microsoft.Qwiq { /// /// Wrapper around the TFS WorkItem. This exists so that every agent doesn't need to reference all the TFS libraries. /// - public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable + public interface IWorkItem : IWorkItemCommon, IIdentifiable { new int AttachedFileCount { get; } @@ -20,6 +22,9 @@ public interface IWorkItem : IWorkItemCommon, IRevision, IIdentifiable new int HyperlinkCount { get; } + [NotNull] + IFieldCollection Fields { get; } + /// /// Gets the ID of this work item. /// diff --git a/src/Qwiq.Core/Revision.cs b/src/Qwiq.Core/Revision.cs index 224e3d28..05e3fa7d 100644 --- a/src/Qwiq.Core/Revision.cs +++ b/src/Qwiq.Core/Revision.cs @@ -9,7 +9,7 @@ public class Revision : IRevision, IRevisionInternal { internal IFieldDefinitionCollection FieldDefinitions { get; } - private Dictionary _values; + private readonly Dictionary _values; private IFieldCollection _fields; @@ -29,15 +29,24 @@ internal Revision([NotNull] IWorkItem workItem, int revision) FieldDefinitions = workItem.Type.FieldDefinitions; } + /// + public virtual IEnumerable Attachments => throw new NotSupportedException(); + public IFieldCollection Fields => _fields ?? (_fields = new FieldCollection(this, FieldDefinitions, (r, d) => new Field(r, d))); + /// + public int Index => Rev.GetValueOrDefault(0); + + /// + public virtual IEnumerable Links => throw new NotSupportedException(); + public int? Id => WorkItem?.Id; public int? Rev { get; } public string Url => WorkItem?.Url; - private IWorkItem WorkItem { get; } + public IWorkItem WorkItem { get; } public virtual object this[string name] { @@ -48,6 +57,12 @@ public virtual object this[string name] } } + /// + public virtual string GetTagLine() + { + throw new NotSupportedException(); + } + object IWorkItemCore.this[string name] { get => this[name]; diff --git a/src/Qwiq.Core/WorkItem.cs b/src/Qwiq.Core/WorkItem.cs index b6c1014e..d51f979c 100644 --- a/src/Qwiq.Core/WorkItem.cs +++ b/src/Qwiq.Core/WorkItem.cs @@ -12,7 +12,7 @@ namespace Microsoft.Qwiq /// /// /// /// - public abstract class WorkItem : WorkItemCommon, IWorkItem, IEquatable + public abstract class WorkItem : WorkItemCommon, IWorkItem, IRevisionInternal, IEquatable { [CanBeNull] private readonly Lazy _lazyType; diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index c25dc90f..da618df9 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -219,6 +219,7 @@ + diff --git a/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/Soap/RevisionTests.cs b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/Soap/RevisionTests.cs new file mode 100644 index 00000000..36febb22 --- /dev/null +++ b/test/Qwiq.Integration.Tests/WorkItemStore/WorkItem/Soap/RevisionTests.cs @@ -0,0 +1,36 @@ +using System.Linq; + +using Microsoft.Qwiq.WorkItemStore.Soap; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Should; + +namespace Microsoft.Qwiq.WorkItemStore.WorkItem.Soap +{ + public abstract class RevisionContextSpecification : SoapWorkItemContextSpecification + { + + } + + [TestClass] + public class Given_a_WorkItem_with_Revisions : RevisionContextSpecification + { + [TestMethod] + public void the_WorkItem_has_Revisions() + { + Result.Rev.ShouldBeGreaterThan(0); + } + + [TestMethod] + public void the_number_of_revisions_is_equal_to_the_revision_objects() + { + Result.Rev.ShouldEqual(Result.Revisions.Count()); + } + + [TestMethod] + public void the_ChangedBy_field_on_the_revision_is_not_empty() + { + Result.Revisions.First()[CoreFieldRefNames.ChangedBy].ShouldNotBeNull(); + } + } +} From ff0bf8e42ddda2142a3505e8518d7fa1cb857458 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 08:55:36 -0700 Subject: [PATCH 230/251] Correct NullReferenceException during REST WorkItemStore disposal In some circumstances the native work item store client may not have been created. During `Dispose`, the value is attempted to be cleaned up, which may cause a `NullReferenceException` to be encountered. Null checks have been added to Dispose method to verify state prior to making a disposal request and value assignment. --- src/Qwiq.Core.Rest/WorkItemStore.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Core.Rest/WorkItemStore.cs b/src/Qwiq.Core.Rest/WorkItemStore.cs index 34bcc6d5..c9f89dbb 100644 --- a/src/Qwiq.Core.Rest/WorkItemStore.cs +++ b/src/Qwiq.Core.Rest/WorkItemStore.cs @@ -184,10 +184,11 @@ private void Dispose(bool disposing) { if (disposing) { - if (NativeWorkItemStore.IsValueCreated) NativeWorkItemStore.Value?.Dispose(); - NativeWorkItemStore = null; - - + if (NativeWorkItemStore != null && NativeWorkItemStore.IsValueCreated) + { + NativeWorkItemStore?.Value?.Dispose(); + NativeWorkItemStore = null; + } } } From 31d8be2fd15979df422d77d386ddcfe73aed7081 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 08:56:26 -0700 Subject: [PATCH 231/251] Sort .csproj to reduce likelihood of merge conflicts --- test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index da618df9..33b1602f 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -221,10 +221,10 @@ + - From 7a2ba9da8bd12635d92afa47e7414aee31f46726 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 09:06:34 -0700 Subject: [PATCH 232/251] Upgrade GitVersionTask Upgrade GitVersionTask to 4.0 Beta 11, the same version of the GitVersion command line used in the AppVeyor CI. This ensures configuration compatibility and version consistency when debugging and building without CI environment. --- src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj | 7 ++++--- src/Qwiq.Core.Rest/packages.config | 2 +- src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj | 7 ++++--- src/Qwiq.Core.Soap/packages.config | 2 +- src/Qwiq.Core/Qwiq.Core.csproj | 7 ++++--- src/Qwiq.Core/packages.config | 2 +- src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj | 7 ++++--- src/Qwiq.Identity.Soap/packages.config | 2 +- src/Qwiq.Identity/Qwiq.Identity.csproj | 7 ++++--- src/Qwiq.Identity/packages.config | 2 +- src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj | 7 ++++--- src/Qwiq.Linq.Identity/packages.config | 2 +- src/Qwiq.Linq/Qwiq.Linq.csproj | 7 ++++--- src/Qwiq.Linq/packages.config | 2 +- src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj | 7 ++++--- src/Qwiq.Mapper.Identity/packages.config | 2 +- src/Qwiq.Mapper/Qwiq.Mapper.csproj | 7 ++++--- src/Qwiq.Mapper/packages.config | 2 +- test/Qwiq.Benchmark/Qwiq.Benchmark.csproj | 7 ++++--- test/Qwiq.Benchmark/packages.config | 2 +- test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj | 7 ++++--- test/Qwiq.Core.Tests/packages.config | 2 +- .../Qwiq.Identity.BenchmarkTests.csproj | 7 ++++--- test/Qwiq.Identity.Benchmark.Tests/packages.config | 2 +- test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj | 7 ++++--- test/Qwiq.Identity.Tests/packages.config | 2 +- test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj | 7 ++++--- test/Qwiq.Integration.Tests/packages.config | 2 +- test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj | 7 ++++--- test/Qwiq.Linq.Tests/packages.config | 2 +- .../Qwiq.Mapper.BenchmarkTests.csproj | 7 ++++--- test/Qwiq.Mapper.Benchmark.Tests/packages.config | 2 +- test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj | 7 ++++--- test/Qwiq.Mapper.Tests/packages.config | 2 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 7 ++++--- test/Qwiq.Mocks/packages.config | 2 +- test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj | 7 ++++--- test/Qwiq.Tests.Common/packages.config | 2 +- 38 files changed, 95 insertions(+), 76 deletions(-) diff --git a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj index eb4c7476..ff0a23f2 100644 --- a/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj +++ b/src/Qwiq.Core.Rest/Qwiq.Client.Rest.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -113,12 +114,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/packages.config b/src/Qwiq.Core.Rest/packages.config index 44048c8d..6a83c44a 100644 --- a/src/Qwiq.Core.Rest/packages.config +++ b/src/Qwiq.Core.Rest/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index e54a4ac2..20531b83 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -229,14 +230,14 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/packages.config b/src/Qwiq.Core.Soap/packages.config index 1f6d3630..c0ae2533 100644 --- a/src/Qwiq.Core.Soap/packages.config +++ b/src/Qwiq.Core.Soap/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index d47a7d45..8b1e6754 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -13,7 +13,8 @@ ..\..\build\rulesets\ship.ruleset true - + + @@ -228,12 +229,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Core/packages.config b/src/Qwiq.Core/packages.config index a2e10a12..eab3ad6c 100644 --- a/src/Qwiq.Core/packages.config +++ b/src/Qwiq.Core/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj index eb6accb1..45af30c5 100644 --- a/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj +++ b/src/Qwiq.Identity.Soap/Qwiq.Identity.Soap.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -196,14 +197,14 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Identity.Soap/packages.config b/src/Qwiq.Identity.Soap/packages.config index 1f6d3630..c0ae2533 100644 --- a/src/Qwiq.Identity.Soap/packages.config +++ b/src/Qwiq.Identity.Soap/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 2cd162cb..1cd38312 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -68,12 +69,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Identity/packages.config b/src/Qwiq.Identity/packages.config index d29d06c8..7ef097d6 100644 --- a/src/Qwiq.Identity/packages.config +++ b/src/Qwiq.Identity/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj index c1e19461..105a5ebc 100644 --- a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj +++ b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -42,12 +43,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Linq.Identity/packages.config b/src/Qwiq.Linq.Identity/packages.config index 9c16c4cc..132b990a 100644 --- a/src/Qwiq.Linq.Identity/packages.config +++ b/src/Qwiq.Linq.Identity/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/Qwiq.Linq/Qwiq.Linq.csproj b/src/Qwiq.Linq/Qwiq.Linq.csproj index ebc14b48..0865e6fa 100644 --- a/src/Qwiq.Linq/Qwiq.Linq.csproj +++ b/src/Qwiq.Linq/Qwiq.Linq.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -83,12 +84,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Linq/packages.config b/src/Qwiq.Linq/packages.config index 9c16c4cc..132b990a 100644 --- a/src/Qwiq.Linq/packages.config +++ b/src/Qwiq.Linq/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj index c863b0ab..16023e3d 100644 --- a/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj +++ b/src/Qwiq.Mapper.Identity/Qwiq.Mapper.Identity.csproj @@ -13,7 +13,8 @@ ..\..\build\rulesets\ship.ruleset $(OutputPath)Microsoft.Qwiq.Mapper.Identity.xml - + + @@ -63,12 +64,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Mapper.Identity/packages.config b/src/Qwiq.Mapper.Identity/packages.config index 469b914c..974e09d8 100644 --- a/src/Qwiq.Mapper.Identity/packages.config +++ b/src/Qwiq.Mapper.Identity/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index fbee51b4..3eecf052 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -63,12 +64,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/src/Qwiq.Mapper/packages.config b/src/Qwiq.Mapper/packages.config index 469b914c..974e09d8 100644 --- a/src/Qwiq.Mapper/packages.config +++ b/src/Qwiq.Mapper/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj index a34895d3..bf90ad65 100644 --- a/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj +++ b/test/Qwiq.Benchmark/Qwiq.Benchmark.csproj @@ -18,7 +18,8 @@ UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -156,17 +157,17 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Benchmark/packages.config b/test/Qwiq.Benchmark/packages.config index a041378f..0c54ee9a 100644 --- a/test/Qwiq.Benchmark/packages.config +++ b/test/Qwiq.Benchmark/packages.config @@ -4,7 +4,7 @@ - + diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj index 21bb271e..ea482175 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj @@ -18,7 +18,8 @@ UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -250,17 +251,17 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/packages.config b/test/Qwiq.Core.Tests/packages.config index 6d48a844..fc27df2c 100644 --- a/test/Qwiq.Core.Tests/packages.config +++ b/test/Qwiq.Core.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj index ac0447f8..4ee0022e 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj +++ b/test/Qwiq.Identity.Benchmark.Tests/Qwiq.Identity.BenchmarkTests.csproj @@ -19,7 +19,8 @@ UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -177,15 +178,15 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Benchmark.Tests/packages.config b/test/Qwiq.Identity.Benchmark.Tests/packages.config index 138d56cc..dad4371f 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/packages.config +++ b/test/Qwiq.Identity.Benchmark.Tests/packages.config @@ -3,7 +3,7 @@ - + diff --git a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj index 454cdbfe..466f2155 100644 --- a/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj +++ b/test/Qwiq.Identity.Tests/Qwiq.Identity.UnitTests.csproj @@ -18,7 +18,8 @@ UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -229,17 +230,17 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/packages.config b/test/Qwiq.Identity.Tests/packages.config index 6d48a844..fc27df2c 100644 --- a/test/Qwiq.Identity.Tests/packages.config +++ b/test/Qwiq.Identity.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj index 33b1602f..ae4298e0 100644 --- a/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj +++ b/test/Qwiq.Integration.Tests/Qwiq.IntegrationTests.csproj @@ -17,7 +17,8 @@ False UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -270,17 +271,17 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Integration.Tests/packages.config b/test/Qwiq.Integration.Tests/packages.config index 6e737bc7..98d952e8 100644 --- a/test/Qwiq.Integration.Tests/packages.config +++ b/test/Qwiq.Integration.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj b/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj index d7bf038e..01b73458 100644 --- a/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj +++ b/test/Qwiq.Linq.Tests/Qwiq.Linq.UnitTests.csproj @@ -18,7 +18,8 @@ UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -64,15 +65,15 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Linq.Tests/packages.config b/test/Qwiq.Linq.Tests/packages.config index 2489202e..c6113f70 100644 --- a/test/Qwiq.Linq.Tests/packages.config +++ b/test/Qwiq.Linq.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj index bcbc674d..5212213e 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj +++ b/test/Qwiq.Mapper.Benchmark.Tests/Qwiq.Mapper.BenchmarkTests.csproj @@ -18,7 +18,8 @@ False UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -179,15 +180,15 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Benchmark.Tests/packages.config b/test/Qwiq.Mapper.Benchmark.Tests/packages.config index 39a784ad..ec447c62 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/packages.config +++ b/test/Qwiq.Mapper.Benchmark.Tests/packages.config @@ -3,7 +3,7 @@ - + diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj index d1a61a3b..c91dc8ab 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj @@ -18,7 +18,8 @@ UnitTest ..\..\build\rulesets\noship.ruleset - + + @@ -98,15 +99,15 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Mapper.Tests/packages.config b/test/Qwiq.Mapper.Tests/packages.config index db327071..847287ab 100644 --- a/test/Qwiq.Mapper.Tests/packages.config +++ b/test/Qwiq.Mapper.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index d8887b00..303f9da3 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -12,7 +12,8 @@ v4.6 ..\..\build\rulesets\ship.ruleset - + + @@ -88,12 +89,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Mocks/packages.config b/test/Qwiq.Mocks/packages.config index 31e72ac2..7f3d2b81 100644 --- a/test/Qwiq.Mocks/packages.config +++ b/test/Qwiq.Mocks/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj index b27f6b50..86e695f4 100644 --- a/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj +++ b/test/Qwiq.Tests.Common/Qwiq.Tests.Common.csproj @@ -13,7 +13,8 @@ v4.6 ..\..\build\rulesets\noship.ruleset - + + @@ -55,15 +56,15 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/test/Qwiq.Tests.Common/packages.config b/test/Qwiq.Tests.Common/packages.config index 6cba4492..097a5d0a 100644 --- a/test/Qwiq.Tests.Common/packages.config +++ b/test/Qwiq.Tests.Common/packages.config @@ -1,6 +1,6 @@  - + From 979d66d1b42669a93ba7dc00297ff7203a47d9ff Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 18:22:23 -0700 Subject: [PATCH 233/251] API Adjustments Some APIs needed adjustments. Discovered during integration with another project. Changes restore signatures of old components (marked as `Obsolete`) to reduce churn necessary for integration. --- src/IEPortal.DotSettings | 49 +++++++++++++++++++ src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs | 5 +- src/Qwiq.Core.Soap/Query.cs | 6 +-- src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj | 1 + src/Qwiq.Core.Soap/WorkItemLinkInfo.cs | 20 ++++++++ src/Qwiq.Core.Soap/WorkItemLinkType.cs | 2 +- src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs | 5 +- src/Qwiq.Core/IWorkItemLinkTypeEnd.cs | 2 +- src/Qwiq.Core/Qwiq.Core.csproj.DotSettings | 2 - src/Qwiq.Core/WorkItemLinkTypeEnd.cs | 2 +- src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs | 10 +--- src/Qwiq.Identity.Soap/Extensions.cs | 28 ++++++++--- src/Qwiq.Identity/Qwiq.Identity.csproj | 6 +++ .../Qwiq.Linq.Identity.csproj | 3 ++ .../Visitors/IdentityMappingVisitor.cs | 5 +- .../IdentityFieldAttributeVisitor.cs | 5 +- test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs | 5 +- 17 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 src/IEPortal.DotSettings create mode 100644 src/Qwiq.Core.Soap/WorkItemLinkInfo.cs delete mode 100644 src/Qwiq.Core/Qwiq.Core.csproj.DotSettings diff --git a/src/IEPortal.DotSettings b/src/IEPortal.DotSettings new file mode 100644 index 00000000..824ba5c8 --- /dev/null +++ b/src/IEPortal.DotSettings @@ -0,0 +1,49 @@ + + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + C:\Users\rimuri\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v08_60bac1d2\SolutionCaches \ No newline at end of file diff --git a/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs index a61a4242..db0dc4b6 100644 --- a/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Rest/WorkItemLinkTypeEnd.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Client.Rest { - internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd + internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd, IIdentifiable { internal WorkItemLinkTypeEnd([NotNull] WorkItemRelationType item) : base(item.ReferenceName) @@ -17,5 +17,8 @@ internal WorkItemLinkTypeEnd([NotNull] WorkItemRelationType item) if (item == null) throw new ArgumentNullException(nameof(item)); Name = string.Intern(item.Name); } + + /// + public int? Id { get; internal set; } } } \ No newline at end of file diff --git a/src/Qwiq.Core.Soap/Query.cs b/src/Qwiq.Core.Soap/Query.cs index 38cda575..df532303 100644 --- a/src/Qwiq.Core.Soap/Query.cs +++ b/src/Qwiq.Core.Soap/Query.cs @@ -50,11 +50,11 @@ public IEnumerable RunLinkQuery() // REVIEW: Create an IWorkItemLinkInfo like IWorkItemLinkTypeEndCollection and IWorkItemCollection var wili = _query.RunLinkQuery(); var retval = new List(wili.Length); - var lt = GetLinkTypes().ToDictionary(k=>k.Id, e=>e); + var lt = GetLinkTypes().ToDictionary(k=>((WorkItemLinkTypeEnd)k).Id, e=>(WorkItemLinkTypeEnd)e); for (var i = 0; i < wili.Length; i++) { - lt.TryGetValue(wili[i].LinkTypeId, out IWorkItemLinkTypeEnd lte) ; - retval.Add(new WorkItemLinkInfo(wili[i].SourceId, wili[i].TargetId, lte)); + lt.TryGetValue(wili[i].LinkTypeId, out WorkItemLinkTypeEnd lte) ; + retval.Add(new WorkItemLinkInfo(wili[i].SourceId, wili[i].TargetId, wili[i].LinkTypeId, lte)); } return retval.AsReadOnly(); diff --git a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj index 20531b83..5395aca5 100644 --- a/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj +++ b/src/Qwiq.Core.Soap/Qwiq.Client.Soap.csproj @@ -208,6 +208,7 @@ + diff --git a/src/Qwiq.Core.Soap/WorkItemLinkInfo.cs b/src/Qwiq.Core.Soap/WorkItemLinkInfo.cs new file mode 100644 index 00000000..b0659bb4 --- /dev/null +++ b/src/Qwiq.Core.Soap/WorkItemLinkInfo.cs @@ -0,0 +1,20 @@ +using System; + +using JetBrains.Annotations; +#pragma warning disable 618 + +namespace Microsoft.Qwiq.Client.Soap +{ + public class WorkItemLinkInfo : Qwiq.WorkItemLinkInfo + { + /// + internal WorkItemLinkInfo(int sourceId, int targetId, int linkTypeId, [CanBeNull] IWorkItemLinkTypeEnd linkTypeEnd) + : base(sourceId, targetId, linkTypeEnd) + { + LinkTypeId = linkTypeId; + } + + [Obsolete("This property is deprecated and will be removed in a future release.")] + public int LinkTypeId { get; } + } +} diff --git a/src/Qwiq.Core.Soap/WorkItemLinkType.cs b/src/Qwiq.Core.Soap/WorkItemLinkType.cs index 9d47d44b..bbe8b29e 100644 --- a/src/Qwiq.Core.Soap/WorkItemLinkType.cs +++ b/src/Qwiq.Core.Soap/WorkItemLinkType.cs @@ -4,7 +4,7 @@ namespace Microsoft.Qwiq.Client.Soap { - internal class WorkItemLinkType : Qwiq.WorkItemLinkType + public class WorkItemLinkType : Qwiq.WorkItemLinkType { internal WorkItemLinkType(Tfs.WorkItemLinkType linkType) : base( diff --git a/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs index 8bd3d859..87b6a172 100644 --- a/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core.Soap/WorkItemLinkTypeEnd.cs @@ -7,7 +7,7 @@ namespace Microsoft.Qwiq.Client.Soap { - internal class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd + public class WorkItemLinkTypeEnd : Qwiq.WorkItemLinkTypeEnd, IIdentifiable { internal WorkItemLinkTypeEnd([NotNull] Tfs.WorkItemLinkTypeEnd end) : base(end.ImmutableName, new Lazy(() => new WorkItemLinkTypeEnd(end.OppositeEnd))) @@ -20,5 +20,8 @@ internal WorkItemLinkTypeEnd([NotNull] Tfs.WorkItemLinkTypeEnd end) IsForwardLink = end.IsForwardLink; Name = end.Name; } + + /// + public int Id { get; } } } \ No newline at end of file diff --git a/src/Qwiq.Core/IWorkItemLinkTypeEnd.cs b/src/Qwiq.Core/IWorkItemLinkTypeEnd.cs index e673ad5d..f39054aa 100644 --- a/src/Qwiq.Core/IWorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core/IWorkItemLinkTypeEnd.cs @@ -1,6 +1,6 @@ namespace Microsoft.Qwiq { - public interface IWorkItemLinkTypeEnd : IIdentifiable + public interface IWorkItemLinkTypeEnd { string ImmutableName { get; } bool IsForwardLink { get; } diff --git a/src/Qwiq.Core/Qwiq.Core.csproj.DotSettings b/src/Qwiq.Core/Qwiq.Core.csproj.DotSettings deleted file mode 100644 index 827beb42..00000000 --- a/src/Qwiq.Core/Qwiq.Core.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - Experimental \ No newline at end of file diff --git a/src/Qwiq.Core/WorkItemLinkTypeEnd.cs b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs index 73da01c3..a354ed23 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEnd.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEnd.cs @@ -41,7 +41,7 @@ internal WorkItemLinkTypeEnd([NotNull] string immutableName) _lazyOpposite = new Lazy(() => !IsForwardLink ? LinkType.ForwardEnd : LinkType.ReverseEnd); } - public int Id { get; internal set; } + public string ImmutableName { get; } diff --git a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs index 60913cae..739f2e9f 100644 --- a/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs +++ b/src/Qwiq.Core/WorkItemLinkTypeEndComparer.cs @@ -21,15 +21,7 @@ public override int GetHashCode(IWorkItemLinkTypeEnd obj) { if (ReferenceEquals(obj, null)) return 0; - unchecked - { - var hash = 27; - - hash = (13 * hash) ^ obj.Id.GetHashCode(); - hash = (13 * hash) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.ImmutableName); - - return hash; - } + return 351 ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.ImmutableName); } // ReSharper disable ClassNeverInstantiated.Local diff --git a/src/Qwiq.Identity.Soap/Extensions.cs b/src/Qwiq.Identity.Soap/Extensions.cs index 1a9bf4af..a1f87304 100644 --- a/src/Qwiq.Identity.Soap/Extensions.cs +++ b/src/Qwiq.Identity.Soap/Extensions.cs @@ -11,22 +11,36 @@ namespace Microsoft.Qwiq.Identity.Soap { public static class Extensions { + /// + /// Gets the identity management service from an instance of . + /// + /// An instance of + /// . + /// teamProjectCollection [NotNull] [JetBrains.Annotations.Pure] - public static IIdentityManagementService GetIdentityManagementService([NotNull] this ITeamProjectCollection tpc) + [PublicAPI] + public static IIdentityManagementService GetIdentityManagementService([NotNull] this ITeamProjectCollection teamProjectCollection) { - Contract.Requires(tpc != null); + Contract.Requires(teamProjectCollection != null); - if (tpc == null) throw new ArgumentNullException(nameof(tpc)); - return ((IInternalTeamProjectCollection)tpc).GetService().AsProxy(); + if (teamProjectCollection == null) throw new ArgumentNullException(nameof(teamProjectCollection)); + return ((IInternalTeamProjectCollection)teamProjectCollection).GetService().AsProxy(); } + /// + /// Gets the identity management service from an instance of . + /// + /// An instance of . + /// . + /// workItemStore [NotNull] [JetBrains.Annotations.Pure] - public static IIdentityManagementService GetIdentityManagementService([NotNull] this IWorkItemStore wis) + [PublicAPI] + public static IIdentityManagementService GetIdentityManagementService([NotNull] this IWorkItemStore workItemStore) { - if (wis == null) throw new ArgumentNullException(nameof(wis)); - return wis.TeamProjectCollection.GetIdentityManagementService(); + if (workItemStore == null) throw new ArgumentNullException(nameof(workItemStore)); + return workItemStore.TeamProjectCollection.GetIdentityManagementService(); } [NotNull] diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index 1cd38312..cb8bfb29 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -18,6 +18,12 @@ + + bin\Debug\Microsoft.Qwiq.Identity.xml + + + bin\Release\Microsoft.Qwiq.Identity.xml + Properties\AssemblyInfo.Common.cs diff --git a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj index 105a5ebc..0445005c 100644 --- a/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj +++ b/src/Qwiq.Linq.Identity/Qwiq.Linq.Identity.csproj @@ -24,6 +24,9 @@ Properties\AssemblyInfo.Common.cs + + Properties\ReSharper.Annotations.cs + diff --git a/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs b/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs index 93e8b13d..f2e7d292 100644 --- a/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs +++ b/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs @@ -1,6 +1,8 @@ using System; using System.Linq.Expressions; +using JetBrains.Annotations; + using Microsoft.Qwiq.Identity; namespace Microsoft.Qwiq.Linq.Visitors @@ -11,6 +13,7 @@ namespace Microsoft.Qwiq.Linq.Visitors /// public class IdentityMappingVisitor : IdentityComboStringVisitor { + [NotNull] private readonly IIdentityValueConverter _valueConverter; /// @@ -18,7 +21,7 @@ public class IdentityMappingVisitor : IdentityComboStringVisitor /// /// An instance of used to convert identity values. /// valueConverter - public IdentityMappingVisitor(IIdentityValueConverter valueConverter) + public IdentityMappingVisitor([NotNull] IIdentityValueConverter valueConverter) { _valueConverter = valueConverter ?? throw new ArgumentNullException(nameof(valueConverter)); } diff --git a/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs b/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs index d70f4292..b5e70e9d 100644 --- a/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs +++ b/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; + using Microsoft.Qwiq.Identity; using Microsoft.Qwiq.Mapper.Attributes; @@ -15,13 +17,14 @@ namespace Microsoft.Qwiq.Linq.Visitors /// public class IdentityFieldAttributeVisitor : ExpressionVisitor { + [NotNull] private readonly IIdentityValueConverter _valueConverter; /// /// Initializes a new instance of the class. /// /// An instance of used to convert identity values. - public IdentityFieldAttributeVisitor(IIdentityValueConverter valueConverter) + public IdentityFieldAttributeVisitor([NotNull] IIdentityValueConverter valueConverter) { Contract.Requires(valueConverter != null); diff --git a/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs b/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs index 66d5e35f..13b5b1df 100644 --- a/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs +++ b/test/Qwiq.Mocks/MockWorkItemLinkTypeEnd.cs @@ -2,7 +2,7 @@ namespace Microsoft.Qwiq.Mocks { - public class MockWorkItemLinkTypeEnd : WorkItemLinkTypeEnd + public class MockWorkItemLinkTypeEnd : WorkItemLinkTypeEnd, IIdentifiable { public MockWorkItemLinkTypeEnd(IWorkItemLinkType linkType, string name, bool isForward, int id = 0) : base(GetValue(linkType, isForward)) @@ -20,5 +20,8 @@ private static string GetValue(IWorkItemLinkType linkType, bool isForward) if (!linkType.IsDirectional) return referenceName; return referenceName + (isForward ? "-Forward" : "-Reverse"); } + + /// + public int Id { get; } } } \ No newline at end of file From 6ff91cd3e78d3411554971a488c9cbe073103d4c Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 8 May 2017 19:21:05 -0700 Subject: [PATCH 234/251] Add MockWorkItemStoreFactory --- test/Qwiq.Mocks/MockWorkItemStoreFactory.cs | 12 ++++++++++++ test/Qwiq.Mocks/Qwiq.Mocks.csproj | 1 + 2 files changed, 13 insertions(+) create mode 100644 test/Qwiq.Mocks/MockWorkItemStoreFactory.cs diff --git a/test/Qwiq.Mocks/MockWorkItemStoreFactory.cs b/test/Qwiq.Mocks/MockWorkItemStoreFactory.cs new file mode 100644 index 00000000..b7fd7840 --- /dev/null +++ b/test/Qwiq.Mocks/MockWorkItemStoreFactory.cs @@ -0,0 +1,12 @@ +using Microsoft.Qwiq.Credentials; + +namespace Microsoft.Qwiq.Mocks +{ + public class MockWorkItemStoreFactory : WorkItemStoreFactory + { + public override IWorkItemStore Create(AuthenticationOptions options) + { + return new MockWorkItemStore(); + } + } +} diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 303f9da3..508a736d 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -63,6 +63,7 @@ + From 3643c9cacfd4d4dacece2caecfe84059becc0c8c Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 31 May 2017 09:55:06 -0700 Subject: [PATCH 235/251] Update default Query Factory in MockWorkItemStore The default query factory created an instance that matched work items as a real query engine might. This caused some problems with tests, so a new query factory was created and set as the default, preserving the legacy MockWorkItemStore behavior. --- test/Qwiq.Mocks/CreateCounterQueryFactory.cs | 42 ++++++++++++ test/Qwiq.Mocks/Extensions.cs | 18 +++++ .../MockFieldDefinitionCollection.cs | 2 +- test/Qwiq.Mocks/MockQuery.cs | 66 +++++++++++++++++++ test/Qwiq.Mocks/MockQueryByWiqlFactory.cs | 48 ++++++++++++++ test/Qwiq.Mocks/MockQueryFactory.cs | 43 +++--------- test/Qwiq.Mocks/MockWorkItemStore.cs | 6 +- test/Qwiq.Mocks/Qwiq.Mocks.csproj | 3 + 8 files changed, 189 insertions(+), 39 deletions(-) create mode 100644 test/Qwiq.Mocks/CreateCounterQueryFactory.cs create mode 100644 test/Qwiq.Mocks/MockQuery.cs create mode 100644 test/Qwiq.Mocks/MockQueryByWiqlFactory.cs diff --git a/test/Qwiq.Mocks/CreateCounterQueryFactory.cs b/test/Qwiq.Mocks/CreateCounterQueryFactory.cs new file mode 100644 index 00000000..9f0906ff --- /dev/null +++ b/test/Qwiq.Mocks/CreateCounterQueryFactory.cs @@ -0,0 +1,42 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; + +namespace Microsoft.Qwiq.Mocks +{ + public class CreateCounterQueryFactory : IQueryFactory + { + [NotNull] private readonly IQueryFactory _delegate; + [NotNull] private readonly IList _queries; + + public CreateCounterQueryFactory([NotNull] IQueryFactory @delegate) + { + _delegate = @delegate ?? throw new ArgumentNullException(nameof(@delegate)); + _queries = new List(); + } + + public int CreateCallCount { get; private set; } + + public IEnumerable Queries => _queries; + + public IQuery Create(string wiql, bool dayPrecision = false) + { + CreateCallCount++; + _queries.Add(wiql); + return _delegate.Create(wiql, dayPrecision); + } + + public IQuery Create(IEnumerable ids, string wiql) + { + CreateCallCount++; + _queries.Add(wiql); + return _delegate.Create(ids, wiql); + } + + public IQuery Create(IEnumerable ids, DateTime? asOf = null) + { + CreateCallCount++; + return _delegate.Create(ids, asOf); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/Extensions.cs b/test/Qwiq.Mocks/Extensions.cs index cfc3444d..b0d0c5e1 100644 --- a/test/Qwiq.Mocks/Extensions.cs +++ b/test/Qwiq.Mocks/Extensions.cs @@ -66,6 +66,24 @@ public static MockWorkItemStore Add( return store; } + public static MockWorkItemStore AddLink(this MockWorkItemStore store, int targetId, int sourceId, string linkType) + { + var child = store.Query(targetId); + + if (child == null) + + throw new ArgumentException($"Parameter {nameof(targetId)} ({targetId}) does not refer to a work item in the store."); + + var lt = store.WorkItemLinkTypes[linkType]; + + child.Links.Add(child.CreateRelatedLink(sourceId, lt.ForwardEnd)); + + store.BatchSave(child); + + + return store; + } + public static MockWorkItemStore AddChildLink(this MockWorkItemStore store, int parentId, int childId) { var child = store.Query(childId); diff --git a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs index 2960e6ff..296be620 100644 --- a/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs +++ b/test/Qwiq.Mocks/MockFieldDefinitionCollection.cs @@ -41,7 +41,7 @@ public override IFieldDefinition this[string name] var def = MockFieldDefinition.Create(name); Add(def); - Trace.TraceWarning($"Added missing field {def.ReferenceName} ({def.Id})"); + Trace.TraceWarning($"Added missing field: (ID: {def.Id}; REF:{def.ReferenceName}"); return def; } diff --git a/test/Qwiq.Mocks/MockQuery.cs b/test/Qwiq.Mocks/MockQuery.cs new file mode 100644 index 00000000..6c7bebea --- /dev/null +++ b/test/Qwiq.Mocks/MockQuery.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using JetBrains.Annotations; + +namespace Microsoft.Qwiq.Mocks +{ + /// + /// Maintains legacy behavior of MockWorkItemStore: WIQL queries return all items, links return all links. Work items are only restricted when querying by ID + /// + public class MockQuery : IQuery + { + [CanBeNull] private readonly string _wiql; + [CanBeNull] private readonly IEnumerable _ids; + [NotNull] private readonly MockWorkItemStore _store; + + public MockQuery( + [NotNull] MockWorkItemStore store, + [CanBeNull] string wiql = null, + [CanBeNull] IEnumerable ids = null) + { + _wiql = wiql; + _ids = ids; + _store = store ?? throw new ArgumentNullException(nameof(store)); + } + + public IWorkItemLinkTypeEndCollection GetLinkTypes() + { + return _store.WorkItemLinkTypes.LinkTypeEnds; + } + + public IEnumerable RunLinkQuery() + { + Trace.TraceInformation("Querying for links " + _wiql ?? ""); + return _store.LinkInfo; + } + + public IWorkItemCollection RunQuery() + { + if (_store._lookup == null || _store._lookup.Count == 0) + { + throw new InvalidOperationException($"{nameof(MockWorkItemStore)} must be initialized with set of {nameof(IWorkItem)}."); + } + + if (_ids == null) + { + Trace.TraceInformation("Querying for work items " + _wiql ?? ""); + return new WorkItemCollection(_store._lookup.Values); + } + + var h = new HashSet(_ids); + h.Remove(0); + var retval = new List(h.Count); + + Trace.TraceInformation("Querying for IDs " + string.Join(", ", h)); + foreach (var id in h) + { + if (_store._lookup.TryGetValue(id, out IWorkItem val)) + { + retval.Add(val); + } + } + return new WorkItemCollection(retval); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockQueryByWiqlFactory.cs b/test/Qwiq.Mocks/MockQueryByWiqlFactory.cs new file mode 100644 index 00000000..5ef8d3eb --- /dev/null +++ b/test/Qwiq.Mocks/MockQueryByWiqlFactory.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using JetBrains.Annotations; + +namespace Microsoft.Qwiq.Mocks +{ + public class MockQueryByWiqlFactory : IQueryFactory + { + [NotNull] private readonly MockWorkItemStore _store; + + public MockQueryByWiqlFactory([NotNull] MockWorkItemStore store) + { + _store = store ?? throw new ArgumentNullException(nameof(store)); + } + + public IQuery Create(string wiql, bool dayPrecision) + { + return new MockQueryByWiql(wiql, _store); + } + + public IQuery Create(IEnumerable ids, string wiql) + { + return new MockQueryByWiql(ids, wiql, _store); + } + + public IQuery Create(IEnumerable ids, DateTime? asOf = null) + { + // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). + // It is used for ASOF + FormattableString ws = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM {WiqlConstants.WorkItemTable}"; + var wiql = ws.ToString(CultureInfo.InvariantCulture); + + if (asOf.HasValue) + { + // If specified DateTime is not UTC convert it to local time based on TFS client TimeZone + if (asOf.Value.Kind != DateTimeKind.Utc) + asOf = DateTime.SpecifyKind( + asOf.Value - _store.TimeZone.GetUtcOffset(asOf.Value), + DateTimeKind.Utc); + FormattableString ao = $" ASOF \'{asOf.Value:u}\'"; + wiql += ao.ToString(CultureInfo.InvariantCulture); + } + + return Create(ids, wiql); + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockQueryFactory.cs b/test/Qwiq.Mocks/MockQueryFactory.cs index 983fcf62..9ff81877 100644 --- a/test/Qwiq.Mocks/MockQueryFactory.cs +++ b/test/Qwiq.Mocks/MockQueryFactory.cs @@ -1,58 +1,31 @@ using System; using System.Collections.Generic; -using System.Globalization; +using JetBrains.Annotations; namespace Microsoft.Qwiq.Mocks { public class MockQueryFactory : IQueryFactory { - private readonly MockWorkItemStore _store; + [NotNull] private readonly MockWorkItemStore _store; - private IList _queries; - - public MockQueryFactory(MockWorkItemStore store) + public MockQueryFactory([NotNull] MockWorkItemStore store) { _store = store ?? throw new ArgumentNullException(nameof(store)); - _queries = new List(); } - public IQuery Create(string wiql, bool dayPrecision) + public IQuery Create(string wiql, bool dayPrecision = false) { - CreateCallCount++; - _queries.Add(wiql); - return new MockQueryByWiql(wiql, _store); + return new MockQuery(_store, wiql); } public IQuery Create(IEnumerable ids, string wiql) { - CreateCallCount++; - _queries.Add(wiql); - return new MockQueryByWiql(ids, wiql, _store); + return new MockQuery(_store, wiql, ids); } public IQuery Create(IEnumerable ids, DateTime? asOf = null) { - // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). - // It is used for ASOF - FormattableString ws = $"SELECT {string.Join(", ", CoreFieldRefNames.All)} FROM WorkItems"; - var wiql = ws.ToString(CultureInfo.InvariantCulture); - - if (asOf.HasValue) - { - // If specified DateTime is not UTC convert it to local time based on TFS client TimeZone - if (asOf.Value.Kind != DateTimeKind.Utc) - asOf = DateTime.SpecifyKind( - asOf.Value - _store.TimeZone.GetUtcOffset(asOf.Value), - DateTimeKind.Utc); - FormattableString ao = $" ASOF \'{asOf.Value:u}\'"; - wiql += ao.ToString(CultureInfo.InvariantCulture); - } - - return Create(ids, wiql); + return new MockQuery(_store, null, ids); } - - public int CreateCallCount { get; private set; } - - public IEnumerable Queries => _queries; } -} +} \ No newline at end of file diff --git a/test/Qwiq.Mocks/MockWorkItemStore.cs b/test/Qwiq.Mocks/MockWorkItemStore.cs index b1154b69..981ffb3c 100644 --- a/test/Qwiq.Mocks/MockWorkItemStore.cs +++ b/test/Qwiq.Mocks/MockWorkItemStore.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; - +using JetBrains.Annotations; using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq.Mocks @@ -33,7 +33,7 @@ public MockWorkItemStore(IEnumerable workItems, IEnumerable tpcFactory, Func queryFactory) + public MockWorkItemStore([InstantHandle] [NotNull] Func tpcFactory, [InstantHandle] [NotNull] Func queryFactory) { if (tpcFactory == null) throw new ArgumentNullException(nameof(tpcFactory)); if (queryFactory == null) throw new ArgumentNullException(nameof(queryFactory)); @@ -107,7 +107,7 @@ public IEnumerable QueryLinks(string wiql, bool dayPrecision return query.RunLinkQuery().ToList().AsReadOnly(); } - internal void BatchSave(IEnumerable workItems) + public void BatchSave(IEnumerable workItems) { // First: Fix up the work items and save them to our dictionary diff --git a/test/Qwiq.Mocks/Qwiq.Mocks.csproj b/test/Qwiq.Mocks/Qwiq.Mocks.csproj index 508a736d..34a57135 100644 --- a/test/Qwiq.Mocks/Qwiq.Mocks.csproj +++ b/test/Qwiq.Mocks/Qwiq.Mocks.csproj @@ -40,6 +40,7 @@ Properties\AssemblyInfo.Common.cs + @@ -52,7 +53,9 @@ + + From 15065169bad5a5cef4ceca5213320bb32c01a014 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 31 May 2017 09:56:02 -0700 Subject: [PATCH 236/251] Add JetBrains annotations and proxy configuration - Add [NotNull] attribute for WorkItemStore field - Add check for proxy creation before returning a proxied Query --- src/Qwiq.Core.Rest/QueryFactory.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Core.Rest/QueryFactory.cs b/src/Qwiq.Core.Rest/QueryFactory.cs index a260aaa2..5cf11e0c 100644 --- a/src/Qwiq.Core.Rest/QueryFactory.cs +++ b/src/Qwiq.Core.Rest/QueryFactory.cs @@ -11,7 +11,7 @@ namespace Microsoft.Qwiq.Client.Rest { internal class QueryFactory : IQueryFactory { - private readonly WorkItemStore _store; + [NotNull] private readonly WorkItemStore _store; private QueryFactory([NotNull] WorkItemStore store) { @@ -22,19 +22,25 @@ private QueryFactory([NotNull] WorkItemStore store) public IQuery Create(string wiql, bool dayPrecision) { - return new Query(new Wiql { Query = wiql }, dayPrecision, _store).AsProxy(); + var q = new Query(new Wiql { Query = wiql }, dayPrecision, _store); + return _store.Configuration.ProxyCreationEnabled + ? q.AsProxy() + : q; } public IQuery Create(IEnumerable ids, string wiql) { - return new Query(ids, new Wiql { Query = wiql }, _store).AsProxy(); + var q = new Query(ids, new Wiql { Query = wiql }, _store); + return _store.Configuration.ProxyCreationEnabled + ? q.AsProxy() + : q; } public IQuery Create(IEnumerable ids, DateTime? asOf = null) { // The WIQL's WHERE and ORDER BY clauses are not used to filter (as we have specified IDs). // It is used for ASOF - FormattableString ws = $"SELECT {string.Join(", ", _store.Configuration.DefaultFields ?? new[] { CoreFieldRefNames.Id })} FROM WorkItems"; + FormattableString ws = $"SELECT {string.Join(", ", _store.Configuration.DefaultFields ?? new[] { CoreFieldRefNames.Id })} FROM {WiqlConstants.WorkItemTable}"; var wiql = ws.ToString(CultureInfo.InvariantCulture); if (asOf.HasValue) From 55df737294a42a5f598dda75929a69a1c8f48881 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 31 May 2017 10:51:28 -0700 Subject: [PATCH 237/251] Add R# annotations, use Lib const - Add JetBrains.Annotations for items - Argument checks use same const values as TFS lib --- src/Qwiq.Core/IdentityDescriptor.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Qwiq.Core/IdentityDescriptor.cs b/src/Qwiq.Core/IdentityDescriptor.cs index ec80dab0..dde8539b 100644 --- a/src/Qwiq.Core/IdentityDescriptor.cs +++ b/src/Qwiq.Core/IdentityDescriptor.cs @@ -1,11 +1,13 @@ using Microsoft.VisualStudio.Services.Identity; using System; +using JetBrains.Annotations; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq { public class IdentityDescriptor : IIdentityDescriptor, IComparable, IEquatable { - private string _identifier; + [NotNull] private string _identifier; /// /// @@ -21,7 +23,7 @@ public class IdentityDescriptor : IIdentityDescriptor, IComparable - public IdentityDescriptor(string identityType, string identifier) + public IdentityDescriptor([NotNull] string identityType, [NotNull] string identifier) { IdentityType = identityType; Identifier = identifier; @@ -33,7 +35,7 @@ public string Identifier private set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); - if (value.Length > 256) throw new ArgumentOutOfRangeException(nameof(value)); + if (value.Length > IdentityConstants.MaxIdLength) throw new ArgumentOutOfRangeException(nameof(value)); _identifier = value; } } @@ -44,16 +46,16 @@ public string IdentityType private set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); - if (value.Length > 128) throw new ArgumentOutOfRangeException(nameof(value)); + if (value.Length > IdentityConstants.MaxTypeLength) throw new ArgumentOutOfRangeException(nameof(value)); IdentityTypeId = IdentityTypeMapper.Instance.GetTypeIdFromName(value); } } - protected internal byte IdentityTypeId { get; set; } + protected internal byte IdentityTypeId { get; private set; } public int CompareTo(IdentityDescriptor other) { - if (this == other) return 0; + if (Equals(this, other)) return 0; if (this == null && other != null) return -1; if (this != null && other == null) return 1; From 2a4d39f43acdc661a3bddb0ba871a49522ef02da Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Wed, 31 May 2017 10:51:28 -0700 Subject: [PATCH 238/251] Add R# annotations, use Lib const - Add JetBrains.Annotations for items - Argument checks use same const values as TFS lib --- src/Qwiq.Core/IdentityDescriptor.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Qwiq.Core/IdentityDescriptor.cs b/src/Qwiq.Core/IdentityDescriptor.cs index ec80dab0..9dffb184 100644 --- a/src/Qwiq.Core/IdentityDescriptor.cs +++ b/src/Qwiq.Core/IdentityDescriptor.cs @@ -1,11 +1,13 @@ using Microsoft.VisualStudio.Services.Identity; using System; +using JetBrains.Annotations; +using Microsoft.VisualStudio.Services.Common; namespace Microsoft.Qwiq { public class IdentityDescriptor : IIdentityDescriptor, IComparable, IEquatable { - private string _identifier; + [NotNull] private string _identifier; /// /// @@ -21,7 +23,7 @@ public class IdentityDescriptor : IIdentityDescriptor, IComparable - public IdentityDescriptor(string identityType, string identifier) + public IdentityDescriptor([NotNull] string identityType, [NotNull] string identifier) { IdentityType = identityType; Identifier = identifier; @@ -33,7 +35,7 @@ public string Identifier private set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); - if (value.Length > 256) throw new ArgumentOutOfRangeException(nameof(value)); + if (value.Length > IdentityConstants.MaxIdLength) throw new ArgumentOutOfRangeException(nameof(value)); _identifier = value; } } @@ -44,12 +46,12 @@ public string IdentityType private set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); - if (value.Length > 128) throw new ArgumentOutOfRangeException(nameof(value)); + if (value.Length > IdentityConstants.MaxTypeLength) throw new ArgumentOutOfRangeException(nameof(value)); IdentityTypeId = IdentityTypeMapper.Instance.GetTypeIdFromName(value); } } - protected internal byte IdentityTypeId { get; set; } + protected internal byte IdentityTypeId { get; private set; } public int CompareTo(IdentityDescriptor other) { From e551efb4dba80c3c62fe388d5ab21bafa4e26454 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 2 Jun 2017 14:01:12 -0700 Subject: [PATCH 239/251] Add tests that confirm no first chance exceptions encountered while parsing +semver: skip --- .../Qwiq.Core.UnitTests.csproj | 1 + .../TypeParserFirstChanceExceptionContext.cs | 28 ++ .../TypeParser/TypeParserTests.cs | 245 +++++++++++++++++- 3 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 test/Qwiq.Core.Tests/TypeParser/TypeParserFirstChanceExceptionContext.cs diff --git a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj index ea482175..4b3cbd6e 100644 --- a/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj +++ b/test/Qwiq.Core.Tests/Qwiq.Core.UnitTests.csproj @@ -205,6 +205,7 @@ + diff --git a/test/Qwiq.Core.Tests/TypeParser/TypeParserFirstChanceExceptionContext.cs b/test/Qwiq.Core.Tests/TypeParser/TypeParserFirstChanceExceptionContext.cs new file mode 100644 index 00000000..69a8a7dd --- /dev/null +++ b/test/Qwiq.Core.Tests/TypeParser/TypeParserFirstChanceExceptionContext.cs @@ -0,0 +1,28 @@ +using System; +using JetBrains.Annotations; + +namespace Microsoft.Qwiq +{ + public abstract class TypeParserFirstChanceExceptionContext : TypeParserTestsContext + { + [CanBeNull] + public Exception FirstChanceException { get; set; } + + public override void Given() + { + base.Given(); + AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException; + } + + public override void Cleanup() + { + base.Cleanup(); + AppDomain.CurrentDomain.FirstChanceException -= CurrentDomain_FirstChanceException; + } + + private void CurrentDomain_FirstChanceException(object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e) + { + FirstChanceException = e.Exception; + } + } +} \ No newline at end of file diff --git a/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs b/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs index 5c263a36..5fee3bb7 100644 --- a/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs +++ b/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs @@ -1,6 +1,6 @@ using System; using System.Xml; - +using JetBrains.Annotations; using Microsoft.VisualStudio.TestTools.UnitTesting; using Should; @@ -144,18 +144,253 @@ public void default_value_is_returned() [TestClass] // ReSharper disable once InconsistentNaming - public class when_parsing_an_empty_string_nonnullable_double : TypeParserTestsContext + public class when_parsing_an_empty_string_nonnullable_bool : TypeParserFirstChanceExceptionContext { public override void When() { - Expected = 11d; - Actual = Parser.Parse("", 11); + Expected = false; + Actual = Parser.Parse(""); } [TestMethod] - public void default_value_is_returned() + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_SByte : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (sbyte)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_byte : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (byte)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_short : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (short)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_ushort : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (ushort)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_double : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = 0d; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_uint : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (uint)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_int : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = 0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_string : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = string.Empty; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_long : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = 0L; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_ulong : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (ulong)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_float : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (float)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_decimal : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = (decimal)0; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_DateTime : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = DateTime.MinValue; + Actual = Parser.Parse(""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() { Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); } } From 3ca919ffd8a37529f0ba19778351649b63e4667e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 2 Jun 2017 13:50:01 -0700 Subject: [PATCH 240/251] Add JetBrains.Annotations for R# +semver: skip --- src/Qwiq.Core/ITypeParser.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Qwiq.Core/ITypeParser.cs b/src/Qwiq.Core/ITypeParser.cs index c6063489..88db4a07 100644 --- a/src/Qwiq.Core/ITypeParser.cs +++ b/src/Qwiq.Core/ITypeParser.cs @@ -1,12 +1,13 @@ using System; +using JetBrains.Annotations; namespace Microsoft.Qwiq { public interface ITypeParser { - object Parse(Type destinationType, object value, object defaultValue); - object Parse(Type destinationType, object input); - T Parse(object value); - T Parse(object value, T defaultValue); + object Parse([CanBeNull] Type destinationType, [CanBeNull] object value, [CanBeNull] object defaultValue); + object Parse([CanBeNull] Type destinationType, [CanBeNull] object input); + T Parse([CanBeNull] object value); + T Parse([CanBeNull] object value, [CanBeNull] T defaultValue); } } From 0a6a4e8ef18204d16d8ce7c12b424fc0431645c6 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Fri, 2 Jun 2017 14:01:43 -0700 Subject: [PATCH 241/251] Update type conversion to prevent FirstChanceExceptions Update implementations of `object ParseImpl(Type, object)` and `object ParseImpl(Type, object, object)` to get the underlying type code for the destination and perform special checks against the value to be converted. If the type code is one that is known to not accept an empty string and the value to be converted is an empty string, then either get the default value of the destination type or return the supplied default value. +semver: patch --- src/Qwiq.Core/TypeParser.cs | 96 +++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/src/Qwiq.Core/TypeParser.cs b/src/Qwiq.Core/TypeParser.cs index 7c7e0267..52472c2a 100644 --- a/src/Qwiq.Core/TypeParser.cs +++ b/src/Qwiq.Core/TypeParser.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -17,11 +18,13 @@ private TypeParser() public object Parse(Type destinationType, object value, object defaultValue) { + if (destinationType == null) throw new ArgumentNullException(nameof(destinationType)); return ParseImpl(destinationType, value, defaultValue); } public object Parse(Type destinationType, object input) { + if (destinationType == null) throw new ArgumentNullException(nameof(destinationType)); return ParseImpl(destinationType, input); } @@ -45,31 +48,93 @@ private static bool IsGenericNullable(Type type) return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } - private static object ParseImpl(Type destinationType, object value) + [CanBeNull] + private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] object value) { - var defaultValueFactory = new Lazy(() => GetDefaultValueOfType(destinationType)); - // If the incoming value is null, return the default value - if (ValueRepresentsNull(value)) return defaultValueFactory.Value; + if (ValueRepresentsNull(value)) return GetDefaultValueOfType(destinationType); + + var valueType = value.GetType(); // Quit if no type conversion is actually required - if (value.GetType() == destinationType) return value; + if (valueType == destinationType) return value; if (destinationType.IsInstanceOfType(value)) return value; + + switch (Type.GetTypeCode(destinationType)) + { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.DateTime: + var destNullable = IsGenericNullable(destinationType); + if (!destNullable && valueType == typeof(string)) + { + var val = (string)value; + if (string.IsNullOrEmpty(val)) + { + return GetDefaultValueOfType(destinationType); + } + } + break; + } + if (TryConvert(destinationType, value, out object result)) return result; - if (IsGenericNullable(destinationType) && defaultValueFactory.Value == null) return null; - if (TryConvert(destinationType, defaultValueFactory.Value, out result)) return result; + + var defaultValue = GetDefaultValueOfType(destinationType); + if (IsGenericNullable(destinationType) && defaultValue == null) return null; + if (TryConvert(destinationType, defaultValue, out result)) return result; return null; } - private static object ParseImpl(Type destinationType, object value, object defaultValue) + [CanBeNull] + private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] object value, [CanBeNull] object defaultValue) { // If the incoming value is null, return the default value if (ValueRepresentsNull(value)) return defaultValue; + var valueType = value.GetType(); + // Quit if no type conversion is actually required - if (value.GetType() == destinationType) return value; + if (valueType == destinationType) return value; if (destinationType.IsInstanceOfType(value)) return value; + + switch (Type.GetTypeCode(destinationType)) + { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.DateTime: + var destNullable = IsGenericNullable(destinationType); + if (!destNullable && valueType == typeof(string)) + { + var val = (string)value; + if (string.IsNullOrEmpty(val)) + { + return defaultValue; + } + } + break; + } + if (TryConvert(destinationType, value, out object result)) return result; if (IsGenericNullable(destinationType) && defaultValue == null) return null; if (TryConvert(destinationType, defaultValue, out result)) return result; @@ -88,7 +153,7 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } @@ -102,7 +167,7 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } @@ -115,7 +180,7 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable CatchAllClause catch - // ReSharper restore CatchAllClause + // ReSharper restore CatchAllClause { } @@ -131,7 +196,7 @@ private static bool TryConvert(Type destinationType, object value, out object re } // ReSharper disable EmptyGeneralCatchClause catch - // ReSharper restore EmptyGeneralCatchClause + // ReSharper restore EmptyGeneralCatchClause { } } @@ -140,14 +205,15 @@ private static bool TryConvert(Type destinationType, object value, out object re return false; } - private static bool ValueRepresentsNull(object value) + [ContractAnnotation("value:null => true")] + private static bool ValueRepresentsNull([CanBeNull] object value) { return value == null || value == DBNull.Value; } // ReSharper disable ClassNeverInstantiated.Local private class Nested - // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore ClassNeverInstantiated.Local { // ReSharper disable MemberHidesStaticFromOuterClass internal static readonly ITypeParser Instance = new TypeParser(); From b0103f3b12d85193dffaac0213b7b97d56557a69 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 5 Jun 2017 14:37:53 -0700 Subject: [PATCH 242/251] Add additional checks for AttributeMapper - Add many checks prior to field assignment: - Coalesce nullsub when field value is null and a nullsub is provided - If the field value is a string, check that it is null, empty, or white space and use nullsub provided. This prevents sending an empty string "" to convert into a type (System.Double) and cause a System.BadFormatException to be thrown. - If the field value is still empty: - If the destination type is a value type, is a generic, is a string, or an object, do not perform any more work as they can use the default initialized value - If conversion is specified, convert to the destination type using TypeParser - If during conversion an exception is encountered, a new AttributeMapException is thrown containing information about the error. +semver: patch --- src/Qwiq.Core/Qwiq.Core.csproj | 1 + src/Qwiq.Core/TypeExtensions.cs | 81 +++++++ src/Qwiq.Core/TypeParser.cs | 99 +++++++-- src/Qwiq.Mapper/AttributeMapException.cs | 22 ++ .../Attributes/AttributeMapperStrategy.cs | 203 ++++++++---------- .../NoExceptionAttributeMapperStrategy.cs | 64 ++++++ src/Qwiq.Mapper/Qwiq.Mapper.csproj | 2 + .../TypeParser/TypeParserTests.cs | 130 ++++++++++- .../PocoMapping.cs | 3 +- .../PocoMappingWithLinks.cs | 3 +- test/Qwiq.Mapper.Tests/GlobalSuppressions.cs | 10 + .../Linq/QueryableContextSpecification.cs | 2 +- .../Qwiq.Mapper.UnitTests.csproj | 1 + test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs | 115 +++++++++- 14 files changed, 597 insertions(+), 139 deletions(-) create mode 100644 src/Qwiq.Core/TypeExtensions.cs create mode 100644 src/Qwiq.Mapper/AttributeMapException.cs create mode 100644 src/Qwiq.Mapper/Attributes/NoExceptionAttributeMapperStrategy.cs create mode 100644 test/Qwiq.Mapper.Tests/GlobalSuppressions.cs diff --git a/src/Qwiq.Core/Qwiq.Core.csproj b/src/Qwiq.Core/Qwiq.Core.csproj index 8b1e6754..64af66eb 100644 --- a/src/Qwiq.Core/Qwiq.Core.csproj +++ b/src/Qwiq.Core/Qwiq.Core.csproj @@ -192,6 +192,7 @@ + diff --git a/src/Qwiq.Core/TypeExtensions.cs b/src/Qwiq.Core/TypeExtensions.cs new file mode 100644 index 00000000..cc8db686 --- /dev/null +++ b/src/Qwiq.Core/TypeExtensions.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace System +{ + public static class TypeExtensions + { + // Default values of value types return by the default ctor + // See https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/default-values-table + private static readonly Dictionary DefaultValuesForTypes = new Dictionary + { + [typeof(bool)] = false, + [typeof(bool?)] = null, + [typeof(byte)] = (byte)0, + [typeof(byte?)] = null, + [typeof(char)] = '\0', + [typeof(decimal)] = 0.0M, + [typeof(decimal?)] = null, + [typeof(double)] = 0.0D, + [typeof(double?)] = null, + [typeof(float)] = 0.0F, + [typeof(float?)] = null, + [typeof(int)] = 0, + [typeof(int?)] = null, + [typeof(long)] = 0L, + [typeof(long?)] = null, + [typeof(sbyte)] = (sbyte)0, + [typeof(sbyte?)] = null, + [typeof(short)] = (short)0, + [typeof(short?)] = null, + [typeof(uint)] = (uint)0, + [typeof(uint?)] = null, + [typeof(ulong)] = (ulong)0, + [typeof(ulong?)] = null, + [typeof(ushort)] = (ushort)0, + [typeof(ushort?)] = null, + [typeof(DateTime)] = DateTime.MinValue, + [typeof(DateTime?)] = null, + [typeof(DateTimeOffset)] = DateTimeOffset.MinValue, + [typeof(DateTimeOffset?)] = null, + }; + + public static bool CanAcceptNull([NotNull] this Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + if (IsGenericNullable(type)) + { + return true; + } + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Object: + case TypeCode.String: + return true; + } + + return false; + } + + [CanBeNull] + public static object GetDefaultValueOfType([NotNull] this Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + if (DefaultValuesForTypes.TryGetValue(type, out object retval)) + { + return retval; + } + + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + public static bool IsGenericNullable([NotNull] this Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Core/TypeParser.cs b/src/Qwiq.Core/TypeParser.cs index 52472c2a..d90f1d41 100644 --- a/src/Qwiq.Core/TypeParser.cs +++ b/src/Qwiq.Core/TypeParser.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using System; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Microsoft.Qwiq @@ -19,6 +20,12 @@ private TypeParser() public object Parse(Type destinationType, object value, object defaultValue) { if (destinationType == null) throw new ArgumentNullException(nameof(destinationType)); + var defaultValueType = defaultValue?.GetType(); + if (defaultValueType != null && destinationType != defaultValueType) + { + Trace.TraceWarning($"The type of parameter {nameof(defaultValue)} ({defaultValueType}) does not match the destination type ({destinationType}. An additional conversion is required to convert to {destinationType}."); + } + return ParseImpl(destinationType, value, defaultValue); } @@ -38,21 +45,19 @@ public T Parse(object value, T defaultValue) return (T)Parse(typeof(T), value, defaultValue); } - private static object GetDefaultValueOfType(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - - private static bool IsGenericNullable(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); - } - [CanBeNull] private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] object value) { - // If the incoming value is null, return the default value - if (ValueRepresentsNull(value)) return GetDefaultValueOfType(destinationType); + var valueIsNull = ValueRepresentsNull(value); + var canAcceptNull = destinationType.CanAcceptNull(); + + if (valueIsNull) + { + if (canAcceptNull) + return null; + + return destinationType.GetDefaultValueOfType(); + } var valueType = value.GetType(); @@ -75,13 +80,13 @@ private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] obje case TypeCode.Double: case TypeCode.Decimal: case TypeCode.DateTime: - var destNullable = IsGenericNullable(destinationType); + var destNullable = destinationType.IsGenericNullable(); if (!destNullable && valueType == typeof(string)) { var val = (string)value; if (string.IsNullOrEmpty(val)) { - return GetDefaultValueOfType(destinationType); + return destinationType.GetDefaultValueOfType(); } } break; @@ -89,18 +94,48 @@ private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] obje if (TryConvert(destinationType, value, out object result)) return result; - var defaultValue = GetDefaultValueOfType(destinationType); - if (IsGenericNullable(destinationType) && defaultValue == null) return null; + var defaultValue = destinationType.GetDefaultValueOfType(); + if (destinationType.IsGenericNullable() && defaultValue == null) return null; + + if (defaultValue != null) + { + var defaultValueType = defaultValue.GetType(); + if (defaultValueType == destinationType) + { + // No conversion required + return defaultValue; + } + } + if (TryConvert(destinationType, defaultValue, out result)) return result; return null; } [CanBeNull] - private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] object value, [CanBeNull] object defaultValue) + private static object ParseImpl( + [NotNull] Type destinationType, + [CanBeNull] object value, + [CanBeNull] object defaultValue) { - // If the incoming value is null, return the default value - if (ValueRepresentsNull(value)) return defaultValue; + var valueIsNull = ValueRepresentsNull(value); + var defaultValueIsNull = ValueRepresentsNull(defaultValue); + var canAcceptNull = destinationType.CanAcceptNull(); + + if (valueIsNull) + { + if (defaultValueIsNull) + { + if (canAcceptNull) + return null; + + // A value type cannot have a null return + throw new InvalidOperationException($"The type {destinationType} cannot have a null value.", new ArgumentNullException(nameof(value), new ArgumentNullException(nameof(defaultValue)))); + } + + // If the incoming value is null, return the default value + return defaultValue; + } var valueType = value.GetType(); @@ -123,20 +158,38 @@ private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] obje case TypeCode.Double: case TypeCode.Decimal: case TypeCode.DateTime: - var destNullable = IsGenericNullable(destinationType); + var destNullable = destinationType.IsGenericNullable(); if (!destNullable && valueType == typeof(string)) { var val = (string)value; if (string.IsNullOrEmpty(val)) { - return defaultValue; + if (defaultValueIsNull) + { + return destinationType.GetDefaultValueOfType(); + } + else + { + return defaultValue; + } } } break; } if (TryConvert(destinationType, value, out object result)) return result; - if (IsGenericNullable(destinationType) && defaultValue == null) return null; + if (destinationType.IsGenericNullable() && defaultValue == null) return null; + + if (defaultValue != null) + { + var defaultValueType = defaultValue.GetType(); + if (defaultValueType == destinationType) + { + // No conversion required + return defaultValue; + } + } + if (TryConvert(destinationType, defaultValue, out result)) return result; return null; @@ -144,7 +197,7 @@ private static object ParseImpl([NotNull] Type destinationType, [CanBeNull] obje private static bool TryConvert(Type destinationType, object value, out object result) { - if (IsGenericNullable(destinationType)) + if (destinationType.IsGenericNullable()) try { var converter = new NullableConverter(destinationType); diff --git a/src/Qwiq.Mapper/AttributeMapException.cs b/src/Qwiq.Mapper/AttributeMapException.cs new file mode 100644 index 00000000..34254604 --- /dev/null +++ b/src/Qwiq.Mapper/AttributeMapException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Microsoft.Qwiq.Mapper +{ + public class AttributeMapException : Exception + { + public AttributeMapException() + { + } + + public AttributeMapException(string message) + :base(message) + { + } + + public AttributeMapException(string message, Exception inner) + :base(message, inner) + { + + } + } +} diff --git a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs index ef2e9aaa..74c35e8c 100644 --- a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs @@ -1,4 +1,5 @@ using FastMember; +using JetBrains.Annotations; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -10,80 +11,102 @@ namespace Microsoft.Qwiq.Mapper.Attributes { public class AttributeMapperStrategy : WorkItemMapperStrategyBase { - private readonly IPropertyInspector _inspector; - private readonly ITypeParser _typeParser; - private static readonly ConcurrentDictionary PropertyInfoFields = new ConcurrentDictionary(); private static readonly ConcurrentDictionary, List> PropertiesThatExistOnWorkItem = new ConcurrentDictionary, List>(); + private static readonly ConcurrentDictionary PropertyInfoFields = new ConcurrentDictionary(); + [NotNull] private readonly IPropertyInspector _inspector; + [NotNull] private readonly ITypeParser _typeParser; - public AttributeMapperStrategy(IPropertyInspector inspector, ITypeParser typeParser) - { - _inspector = inspector; - _typeParser = typeParser; - } - private static FieldDefinitionAttribute PropertyInfoFieldCache(IPropertyInspector inspector, PropertyInfo property) + public AttributeMapperStrategy([NotNull] IPropertyInspector inspector) + : this(inspector, TypeParser.Default) { - return PropertyInfoFields.GetOrAdd( - property, - info => inspector.GetAttribute(property)); } - private static IEnumerable PropertiesOnWorkItemCache(IPropertyInspector inspector, IWorkItem workItem, Type targetType, Type attributeType) + public AttributeMapperStrategy([NotNull] IPropertyInspector inspector, [NotNull] ITypeParser typeParser) { - // Composite key: work item type and target type - - var workItemType = workItem.Type.Name; - var key = new Tuple(workItemType, targetType.TypeHandle); - - return PropertiesThatExistOnWorkItem.GetOrAdd( - key, - tuple => - { - return - inspector.GetAnnotatedProperties(targetType, typeof(FieldDefinitionAttribute)) - .Select( - property => - new { property, fieldName = PropertyInfoFieldCache(inspector, property)?.FieldName }) - .Where( - @t => - !string.IsNullOrEmpty(@t.fieldName) && workItem.Fields.Contains(@t.fieldName)) - .Select(@t => @t.property) - .ToList(); - }); + _typeParser = typeParser ?? throw new ArgumentNullException(nameof(typeParser)); + _inspector = inspector ?? throw new ArgumentNullException(nameof(inspector)); } public override void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) { var targetWorkItemType = typeof(T); - var accessor = TypeAccessor.Create(targetWorkItemType, true); foreach (var workItemMapping in workItemMappings) { var sourceWorkItem = workItemMapping.Key; var targetWorkItem = workItemMapping.Value; - MapImpl(targetWorkItemType, sourceWorkItem, accessor, targetWorkItem); + MapImpl(targetWorkItemType, sourceWorkItem, targetWorkItem); } } public override void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) { - var accessor = TypeAccessor.Create(targetWorkItemType, true); - foreach (var workItemMapping in workItemMappings) { var sourceWorkItem = workItemMapping.Key; var targetWorkItem = workItemMapping.Value; - MapImpl(targetWorkItemType, sourceWorkItem, accessor, targetWorkItem); + MapImpl(targetWorkItemType, sourceWorkItem, targetWorkItem); + } + } + + protected internal virtual void AssignFieldValue( + [NotNull] Type targetWorkItemType, + [NotNull] IWorkItem sourceWorkItem, + [NotNull] object targetWorkItem, + [NotNull] PropertyInfo property, + [NotNull] string fieldName, + bool convert, + [CanBeNull] object nullSub, + [CanBeNull] object fieldValue) + { + // Coalesce fieldValue and nullSub + + if (fieldValue == null && nullSub != null) + { + fieldValue = nullSub; + } + else + { + if (fieldValue is string value && string.IsNullOrWhiteSpace(value)) + fieldValue = nullSub; + } + + var destType = property.PropertyType; + + if (fieldValue == null && destType.IsValueType) + { + // Value types do not accept null; don't do any work + return; + } + + if (fieldValue == null && destType.CanAcceptNull()) + { + // Destination is a nullable or can take null; don't do any work + return; + } + + var sourceType = fieldValue.GetType(); + if (convert) + { + try + { + fieldValue = _typeParser.Parse(destType, fieldValue); + } + catch (Exception e) + { + throw new AttributeMapException($"Unable to convert field {fieldName} on work item {sourceWorkItem.Id}: {sourceWorkItem.WorkItemType} to property {property.Name} on {targetWorkItemType}.", e); + } } + + var accessor = TypeAccessor.Create(targetWorkItemType, true); + accessor[targetWorkItem, property.Name] = fieldValue; } - private void MapImpl(Type targetWorkItemType, IWorkItem sourceWorkItem, TypeAccessor accessor, object targetWorkItem) + protected internal virtual void MapImpl(Type targetWorkItemType, IWorkItem sourceWorkItem, object targetWorkItem) { -#if DEBUG - Trace.TraceInformation("{0}: Mapping {1}", GetType().Name, sourceWorkItem.Id); -#endif var properties = PropertiesOnWorkItemCache( _inspector, sourceWorkItem, @@ -100,75 +123,39 @@ private void MapImpl(Type targetWorkItemType, IWorkItem sourceWorkItem, TypeAcce var nullSub = a.NullSubstitute; var fieldValue = sourceWorkItem[fieldName]; - try - { - if (convert) - { - try - { - fieldValue = _typeParser.Parse(property.PropertyType, fieldValue, nullSub); - } - catch (Exception) - { - try - { - Trace.TraceWarning( - "Could not convert value of field '{0}' ({1}) to type {2}", - fieldName, - fieldValue.GetType().Name, - property.PropertyType.Name); - } - catch (Exception) - { - // Best effort - } - } - } - - if (fieldValue == null && nullSub != null) - { - fieldValue = nullSub; - } + AssignFieldValue(targetWorkItemType, sourceWorkItem, targetWorkItem, property, fieldName, convert, nullSub, fieldValue); + } + } - accessor[targetWorkItem, property.Name] = fieldValue; + private static IEnumerable PropertiesOnWorkItemCache(IPropertyInspector inspector, IWorkItem workItem, Type targetType, Type attributeType) + { + // Composite key: work item type and target type - } - catch(NullReferenceException) when (fieldValue == null) - { - // This is most likely the cause of the field being null and the target property type not accepting nulls - // For example: mapping null to an int instead of int? + var workItemType = workItem.Type.Name; + var key = new Tuple(workItemType, targetType.TypeHandle); - try - { - Trace.TraceWarning( - "Could not map field '{0}' from type '{1}' to type '{2}'. Target '{2}.{3}' does not accept null values.", - fieldName, - sourceWorkItem.Type.Name, - targetWorkItemType.Name, - $"{property.Name} ({property.PropertyType.FullName})"); - } - catch (Exception) - { - // Best effort - } - } - catch (Exception e) - { - try - { - Trace.TraceWarning( - "Could not map field '{0}' from type '{1}' to type '{2}'. {3}", - fieldName, - sourceWorkItem.Type.Name, - targetWorkItemType.Name, - e.Message); - } - catch (Exception) + return PropertiesThatExistOnWorkItem.GetOrAdd( + key, + tuple => { - // Best effort - } - } - } + return + inspector.GetAnnotatedProperties(targetType, typeof(FieldDefinitionAttribute)) + .Select( + property => + new { property, fieldName = PropertyInfoFieldCache(inspector, property)?.FieldName }) + .Where( + @t => + !string.IsNullOrEmpty(@t.fieldName) && workItem.Fields.Contains(@t.fieldName)) + .Select(@t => @t.property) + .ToList(); + }); + } + + private static FieldDefinitionAttribute PropertyInfoFieldCache(IPropertyInspector inspector, PropertyInfo property) + { + return PropertyInfoFields.GetOrAdd( + property, + info => inspector.GetAttribute(property)); } } -} +} \ No newline at end of file diff --git a/src/Qwiq.Mapper/Attributes/NoExceptionAttributeMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/NoExceptionAttributeMapperStrategy.cs new file mode 100644 index 00000000..c2cb484f --- /dev/null +++ b/src/Qwiq.Mapper/Attributes/NoExceptionAttributeMapperStrategy.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.Qwiq.Mapper.Attributes +{ + public class NoExceptionAttributeMapperStrategy : AttributeMapperStrategy + { + public NoExceptionAttributeMapperStrategy([NotNull] IPropertyInspector inspector) : base(inspector) + { + } + + public NoExceptionAttributeMapperStrategy([NotNull] IPropertyInspector inspector, [NotNull] ITypeParser typeParser) + :base(inspector, typeParser) + { + + } + + protected internal override void AssignFieldValue(Type targetWorkItemType, IWorkItem sourceWorkItem, object targetWorkItem, PropertyInfo property, string fieldName, bool convert, object nullSub, object fieldValue) + { + try + { + base.AssignFieldValue(targetWorkItemType, sourceWorkItem, targetWorkItem, property, fieldName, convert, nullSub, fieldValue); + } + catch (NullReferenceException) when (fieldValue == null) + { + // This is most likely the cause of the field being null and the target property type not accepting nulls + // For example: mapping null to an int instead of int? + + try + { + Trace.TraceWarning( + "Could not map field '{0}' from type '{1}' to type '{2}'. Target '{2}.{3}' does not accept null values.", + fieldName, + sourceWorkItem.WorkItemType, + targetWorkItemType.Name, + $"{property.Name} ({property.PropertyType.FullName})"); + } + catch (Exception) + { + // Best effort + } + } + catch (Exception e) + { + try + { + Trace.TraceWarning( + "Could not map field '{0}' from type '{1}' to type '{2}.{3}'. {4}", + fieldName, + sourceWorkItem.WorkItemType, + targetWorkItemType.Name, + $"{property.Name} ({property.PropertyType.FullName})", + e.Message); + } + catch (Exception) + { + // Best effort + } + } + } + } +} diff --git a/src/Qwiq.Mapper/Qwiq.Mapper.csproj b/src/Qwiq.Mapper/Qwiq.Mapper.csproj index 3eecf052..022a1f49 100644 --- a/src/Qwiq.Mapper/Qwiq.Mapper.csproj +++ b/src/Qwiq.Mapper/Qwiq.Mapper.csproj @@ -29,6 +29,7 @@ Properties\AssemblyInfo.Common.cs + @@ -42,6 +43,7 @@ + diff --git a/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs b/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs index 5fee3bb7..dcc2219a 100644 --- a/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs +++ b/test/Qwiq.Core.Tests/TypeParser/TypeParserTests.cs @@ -25,7 +25,7 @@ public void value_is_converted() [TestClass] // ReSharper disable once InconsistentNaming - public class when_parsing_an_enum_value : TypeParserTestsContext + public class when_parsing_an_enum_value_Generic : TypeParserTestsContext { public override void When() { @@ -40,6 +40,52 @@ public void enum_value_is_returned() } } + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_enum_value_null_with_defaultValue : TypeParserTestsContext + { + public override void When() + { + Expected = (Formatting)0; + Actual = Parser.Parse(typeof(Formatting), null, Formatting.None); + } + + [TestMethod] + public void enum_value_is_returned() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_enum_value_null_defaultValue_null : TypeParserTestsContext + { + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void enum_value_is_returned() + { + Parser.Parse(typeof(Formatting), null, null); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_enum_value_null : TypeParserTestsContext + { + public override void When() + { + Expected = (Formatting)0; + Actual = Parser.Parse(typeof(Formatting), null); + } + + [TestMethod] + public void enum_value_is_returned() + { + Actual.ShouldEqual(Expected); + } + } + [TestClass] // ReSharper disable once InconsistentNaming public class when_parsing_a_null_to_nonnullable_double : TypeParserTestsContext @@ -250,6 +296,24 @@ public void default_value_is_returned_without_exception() } } + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_double_and_null_defaultvalue : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = 0d; + Actual = Parser.Parse(typeof(double), "", null); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + [TestClass] // ReSharper disable once InconsistentNaming public class when_parsing_an_empty_string_nonnullable_uint : TypeParserFirstChanceExceptionContext @@ -379,6 +443,24 @@ public void default_value_is_returned_without_exception() [TestClass] // ReSharper disable once InconsistentNaming public class when_parsing_an_empty_string_nonnullable_DateTime : TypeParserFirstChanceExceptionContext + { + public override void When() + { + Expected = DateTime.MinValue; + Actual = (DateTime)Parser.Parse(typeof(DateTime), (object)""); + } + + [TestMethod] + public void default_value_is_returned_without_exception() + { + Actual.ShouldEqual(Expected); + FirstChanceException.ShouldBeNull(); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_an_empty_string_nonnullable_DateTime_Generic : TypeParserFirstChanceExceptionContext { public override void When() { @@ -394,6 +476,18 @@ public void default_value_is_returned_without_exception() } } + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_a_valid_nonnullable_double_with_nuill_for_value_and_defaultvalue : TypeParserTestsContext + { + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void value_is_parsed_as_double() + { + Parser.Parse(typeof(double), null, null); + } + } + [TestClass] // ReSharper disable once InconsistentNaming public class when_parsing_a_valid_nonnullable_double : TypeParserTestsContext @@ -428,6 +522,40 @@ public void value_is_null() } } + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_a_byte_to_decimal : TypeParserTestsContext + { + public override void When() + { + Expected = (decimal)255; + Actual = Parser.Parse(typeof(decimal), (object)((byte)255)); + } + + [TestMethod] + public void decimal_value_is_expected() + { + Actual.ShouldEqual(Expected); + } + } + + [TestClass] + // ReSharper disable once InconsistentNaming + public class when_parsing_a_null_nullable_double_with_defaultValue_of_decimal : TypeParserTestsContext + { + public override void When() + { + Expected = 1.0M; + Actual = Parser.Parse(typeof(double?), null, 1.0M); + } + + [TestMethod] + public void value_is_defaultValue() + { + Actual.ShouldEqual(Expected); + } + } + [TestClass] // ReSharper disable once InconsistentNaming public class when_parsing_a_valid_nullable_double : TypeParserTestsContext diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs index 2b62590f..c7d89cd8 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMapping.cs @@ -44,9 +44,8 @@ public class Benchmark public void SetupData() { var propertyInspector = new PropertyInspector(new PropertyReflector()); - var typeParser = TypeParser.Default; var mappingStrategies = new IWorkItemMapperStrategy[] - { new AttributeMapperStrategy(propertyInspector, typeParser) }; + { new AttributeMapperStrategy(propertyInspector) }; _mapper = new WorkItemMapper(mappingStrategies); var wis = new MockWorkItemStore(); diff --git a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs index ddb57e0d..4fa59e02 100644 --- a/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs +++ b/test/Qwiq.Mapper.Benchmark.Tests/PocoMappingWithLinks.cs @@ -49,10 +49,9 @@ public void SetupData() new[] { "Revisions", "Item" }); wis.Add(generator.Generate()); var propertyInspector = new PropertyInspector(new PropertyReflector()); - var typeParser = TypeParser.Default; var mappingStrategies = new IWorkItemMapperStrategy[] { - new AttributeMapperStrategy(propertyInspector, typeParser), + new AttributeMapperStrategy(propertyInspector), new WorkItemLinksMapperStrategy(propertyInspector, wis), }; _mapper = new WorkItemMapper(mappingStrategies); diff --git a/test/Qwiq.Mapper.Tests/GlobalSuppressions.cs b/test/Qwiq.Mapper.Tests/GlobalSuppressions.cs new file mode 100644 index 00000000..ade2b106 --- /dev/null +++ b/test/Qwiq.Mapper.Tests/GlobalSuppressions.cs @@ -0,0 +1,10 @@ + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CC0022:Should dispose object", Justification = "", Scope = "member", Target = "~M:Microsoft.Qwiq.Mapper.Foo.Given")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CC0022:Should dispose object", Justification = "", Scope = "member", Target = "~M:Microsoft.Qwiq.Mapper.Given_a_model_with_a_field_of_type_Double_mapped_from_StringEmpty_with_NullSub.Given")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CC0022:Should dispose object", Justification = "", Scope = "member", Target = "~M:Microsoft.Qwiq.Mapper.Given_a_model_with_a_field_of_type_Double_mapped_from_StringEmpty.Given")] + diff --git a/test/Qwiq.Mapper.Tests/Linq/QueryableContextSpecification.cs b/test/Qwiq.Mapper.Tests/Linq/QueryableContextSpecification.cs index 0f6c4421..836997d1 100644 --- a/test/Qwiq.Mapper.Tests/Linq/QueryableContextSpecification.cs +++ b/test/Qwiq.Mapper.Tests/Linq/QueryableContextSpecification.cs @@ -36,7 +36,7 @@ public override void Given() var mapperStrategies = new IWorkItemMapperStrategy[] { - new AttributeMapperStrategy(propertyInspector, TypeParser.Default), + new AttributeMapperStrategy(propertyInspector), new WorkItemLinksMapperStrategy(propertyInspector, workItemStore) }; diff --git a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj index c91dc8ab..0c90d01d 100644 --- a/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj +++ b/test/Qwiq.Mapper.Tests/Qwiq.Mapper.UnitTests.csproj @@ -55,6 +55,7 @@ Properties\AssemblyInfo.Common.cs + diff --git a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs index 4a5efc85..30bf03de 100644 --- a/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs +++ b/test/Qwiq.Mapper.Tests/WorkItemMapperTests.cs @@ -50,6 +50,10 @@ public abstract class WorkItemMapperContext : ContextSpecification { "StringField", "sample" + }, + { + "EmptyStringField", + string.Empty } }; @@ -64,10 +68,9 @@ public abstract class WorkItemMapperContext : ContextSpecification public override void Given() { var propertyInspector = new PropertyInspector(new PropertyReflector()); - var typeParser = TypeParser.Default; var mappingStrategies = new IWorkItemMapperStrategy[] { - new AttributeMapperStrategy(propertyInspector, typeParser), + new AttributeMapperStrategy(propertyInspector), new WorkItemLinksMapperStrategy(propertyInspector, WorkItemStore) }; _workItemMapper = new WorkItemMapper(mappingStrategies); @@ -84,6 +87,114 @@ public override void Cleanup() } } + [TestClass] + public class Given_a_model_with_a_field_of_type_Double_mapped_from_StringEmpty_Requiring_Conversion : WorkItemMapperContext + { + public override void Given() + { + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("EmptyStringField", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; + WorkItemStore = new MockWorkItemStore().Add(SourceWorkItems); + base.Given(); + } + + [TestMethod] + public void the_mapped_property_is_the_substituted_value() + { + Actual.DoubleField.ShouldEqual(0.0d); + } + + [WorkItemType("EmptyStringField")] + public class EmptyStringModel : IIdentifiable + { + [FieldDefinition("Id")] + public int? Id { get;set;} + + [FieldDefinition("EmptyStringField", true)] + public double DoubleField { get;set;} + } + } + + [TestClass] + public class Given_a_model_with_a_field_of_type_Double_mapped_from_StringEmpty_with_NullSub : WorkItemMapperContext + { + public override void Given() + { + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("EmptyStringField", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; + WorkItemStore = new MockWorkItemStore().Add(SourceWorkItems); + base.Given(); + } + + [TestMethod] + public void the_mapped_property_is_the_substituted_value() + { + Actual.DoubleField.ShouldEqual(0.0d); + } + + [WorkItemType("EmptyStringField")] + public class EmptyStringModel : IIdentifiable + { + [FieldDefinition("Id")] + public int? Id { get; set; } + + [FieldDefinition("EmptyStringField", 0.0d)] + public double DoubleField { get; set; } + } + } + + [TestClass] + public class Given_a_model_with_a_field_of_type_Double_mapped_from_StringEmpty : WorkItemMapperContext + { + public override void Given() + { + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("EmptyStringField", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; + WorkItemStore = new MockWorkItemStore().Add(SourceWorkItems); + base.Given(); + } + + [TestMethod] + public void the_mapped_property_is_the_substituted_value() + { + Actual.DoubleField.ShouldEqual(0.0d); + } + + [WorkItemType("EmptyStringField")] + public class EmptyStringModel : IIdentifiable + { + [FieldDefinition("Id")] + public int? Id { get; set; } + + [FieldDefinition("EmptyStringField")] + public double DoubleField { get; set; } + } + } + + [TestClass] + public class Given_a_model_with_a_field_of_type_Double_mapped_from_StringEmpty_Requiring_Conversion_with_Default : WorkItemMapperContext + { + public override void Given() + { + SourceWorkItems = new[] { new MockWorkItem(new MockWorkItemType("EmptyStringField", WorkItemBackingStore.Keys.Select(MockFieldDefinition.Create)), WorkItemBackingStore) }; + WorkItemStore = new MockWorkItemStore().Add(SourceWorkItems); + base.Given(); + } + + [TestMethod] + public void the_mapped_property_is_the_substituted_value() + { + Actual.DoubleField.ShouldEqual(0.0d); + } + + [WorkItemType("EmptyStringField")] + public class EmptyStringModel : IIdentifiable + { + [FieldDefinition("Id")] + public int? Id { get; set; } + + [FieldDefinition("EmptyStringField", true, 0.0d)] + public double DoubleField { get; set; } + } + } + [TestClass] public class when_an_issue_is_mapped_with_a_field_containing_null_that_does_not_accept_null_and_has_a_null_substitute : WorkItemMapperContext { From a0f9da992013d656670de555c39752809e64f786 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 5 Jun 2017 11:35:04 -0700 Subject: [PATCH 243/251] Revert 500d1e946fd06c70a71acab40554d92ea212ea90 Change 500d1e946fd06c70a71acab40554d92ea212ea90 introduced a regression when performing comparisons of `IdentityDescriptor` causing a StackOverflowException. The change made to src/Qwiq.core/IdentityDescriptor.cs has been reverted to correct the issue. +semver: patch --- src/Qwiq.Core/IdentityDescriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Qwiq.Core/IdentityDescriptor.cs b/src/Qwiq.Core/IdentityDescriptor.cs index dde8539b..9dffb184 100644 --- a/src/Qwiq.Core/IdentityDescriptor.cs +++ b/src/Qwiq.Core/IdentityDescriptor.cs @@ -55,7 +55,7 @@ private set public int CompareTo(IdentityDescriptor other) { - if (Equals(this, other)) return 0; + if (this == other) return 0; if (this == null && other != null) return -1; if (this != null && other == null) return 1; From 1e28112f9e3bd2a0df44e6a4363b1aa49f95a84e Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 17:55:22 -0700 Subject: [PATCH 244/251] Add new interfaces for identity value conversion Add explicit contracts for identity type conversion: one for single values, one for bulk. Mark the old method as `Obsolete` --- .../DisplayNameToAliasValueConverter.cs | 29 ++- src/Qwiq.Identity/IIdentityValueConverter.cs | 13 +- .../IdentityAliasValueConverter.cs | 46 ++-- .../IdentityFieldValueConverter.cs | 50 +++++ .../IdentityValueConverterBase.cs | 42 ++++ .../MultipleIdentitiesFoundException.cs | 26 +++ src/Qwiq.Identity/Qwiq.Identity.csproj | 3 + .../Visitors/IdentityMappingVisitor.cs | 6 +- ...ulkIdentityAwareAttributeMapperStrategy.cs | 197 +++++++++++++----- .../IdentityFieldAttributeVisitor.cs | 8 +- .../WorkItemWithFields.cs | 2 +- src/Qwiq.Mapper/AttributeMapException.cs | 74 ++++++- ...entityAwareAttributeMapperStrategyTests.cs | 27 ++- .../IdentityMapperTests.cs | 2 + .../Mocks/IdentityMockType.cs | 4 + .../SoapIdentityMapperContextSpecification.cs | 2 + 16 files changed, 422 insertions(+), 109 deletions(-) create mode 100644 src/Qwiq.Identity/IdentityFieldValueConverter.cs create mode 100644 src/Qwiq.Identity/IdentityValueConverterBase.cs create mode 100644 src/Qwiq.Identity/MultipleIdentitiesFoundException.cs diff --git a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs index 95c036cf..7efce19e 100644 --- a/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs +++ b/src/Qwiq.Identity/DisplayNameToAliasValueConverter.cs @@ -13,9 +13,9 @@ namespace Microsoft.Qwiq.Identity /// /// Converts a representing an identity to an alias /// - /// - public class DisplayNameToAliasValueConverter : IIdentityValueConverter + public class DisplayNameToAliasValueConverter : IdentityValueConverterBase { + private static readonly IReadOnlyDictionary Empty = new Dictionary(); private readonly IIdentityManagementService _identityManagementService; /// @@ -26,25 +26,19 @@ public class DisplayNameToAliasValueConverter : IIdentityValueConverter public DisplayNameToAliasValueConverter([NotNull] IIdentityManagementService identityManagementService) { Contract.Requires(identityManagementService != null); - + _identityManagementService = identityManagementService ?? throw new ArgumentNullException(nameof(identityManagementService)); } - /// - /// Converts the specified to an . - /// - /// The value to convert. - /// A instance whose key is the and value is is equivalent to the value of . - public object Map([CanBeNull] object value) + public override IReadOnlyDictionary Map(IEnumerable values) { - if (value is string stringValue) return GetIdentityNames(stringValue); + if (values == null) return Empty; + return GetIdentityNames(values.ToArray()); + } - if (value is IEnumerable stringArray) return GetIdentityNames(stringArray.ToArray()); - return value; - } - private Dictionary GetIdentityNames(params string[] displayNames) + private Dictionary GetIdentityNames(params string[] displayNames) { return GetAliasesForDisplayNames(displayNames) @@ -56,9 +50,12 @@ private Dictionary GetIdentityNames(params string[] displayNames var retval = kvp.Value.FirstOrDefault(); if (kvp.Value.Length > 1) { - Trace.TraceWarning("Multiple identities found matching '{0}':\r\n\r\n- {1}\r\n\r\nChoosing '{2}'.", kvp.Key, string.Join("\r\n- ", kvp.Value), retval); + var m = + $"Multiple identities found matching '{kvp.Key}'. Please specify one of the following identities:{string.Join("\r\n- ", kvp.Value)}"; + + throw new MultipleIdentitiesFoundException(m); } - return retval; + return (object)retval; }, Comparer.OrdinalIgnoreCase); } diff --git a/src/Qwiq.Identity/IIdentityValueConverter.cs b/src/Qwiq.Identity/IIdentityValueConverter.cs index acf45861..1f3a33e3 100644 --- a/src/Qwiq.Identity/IIdentityValueConverter.cs +++ b/src/Qwiq.Identity/IIdentityValueConverter.cs @@ -1,11 +1,13 @@ +using System; using JetBrains.Annotations; +using System.Collections.Generic; namespace Microsoft.Qwiq.Identity { /// /// Defines a method that converts the value of the implementing reference or value type to another reference or value type. /// - public interface IIdentityValueConverter + public interface IIdentityValueConverter { /// /// Converts the specified to an . @@ -13,6 +15,13 @@ public interface IIdentityValueConverter /// The value to convert. /// An instance whose value is equivalent to the value of . [ContractAnnotation("null => null; notnull => notnull")] - object Map([CanBeNull] object value); + U Map([CanBeNull] T value); + + IReadOnlyDictionary Map(IEnumerable values); + + [Obsolete("This method is depreciated and will be removed in a future version.")] + object Map(object value); } + + } diff --git a/src/Qwiq.Identity/IdentityAliasValueConverter.cs b/src/Qwiq.Identity/IdentityAliasValueConverter.cs index 6920427d..31da0f28 100644 --- a/src/Qwiq.Identity/IdentityAliasValueConverter.cs +++ b/src/Qwiq.Identity/IdentityAliasValueConverter.cs @@ -14,10 +14,10 @@ namespace Microsoft.Qwiq.Identity /// /// Converts a representing an alias to a user principal name. /// - /// /// - public class IdentityAliasValueConverter : IIdentityValueConverter + public class IdentityAliasValueConverter : IdentityValueConverterBase { + private static readonly IReadOnlyDictionary Empty = new Dictionary(); private readonly string[] _domains; private readonly IIdentityManagementService _identityManagementService; @@ -53,29 +53,33 @@ public IdentityAliasValueConverter( _domains = domains; } - /// - /// Converts the specified to an . - /// - /// The value. - /// An instance whose value is equivilent to the value of . - /// "danj" becomes "danj@contoso.com" - [ContractAnnotation("null => null; notnull => notnull")] - public object Map([CanBeNull] object value) + public override IReadOnlyDictionary Map(IEnumerable values) { - if (value is string stringValue) return GetIdentityNames(stringValue).Single(); - - if (value is IEnumerable stringArray) return GetIdentityNames(stringArray.ToArray()); + if (values == null) return Empty; + var r = GetIdentityForAliases(values.ToList(), _tenantId, _domains); + var retval = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var result in r) + { + var v = result.Value as ITeamFoundationIdentity; + retval.Add(result.Key, v?.GetIdentityName() ?? result.Key); + } - return value; + return retval; } - private string[] GetIdentityNames(params string[] aliases) + [Obsolete("This method is depreciated and will be removed in a future version.")] + public override object Map(object value) { - var identities = GetIdentityForAliases(aliases.ToList(), _tenantId, _domains); - return identities.Select(i => i.Value.GetIdentityName() ?? i.Key).ToArray(); + if (value is string stringValue) return Map(stringValue); + if (value is IEnumerable stringArray) + { + return Map(stringArray).Select(s => (string)s.Value).ToArray(); + } + + return value; } - private IDictionary GetIdentityForAliases( + private Dictionary GetIdentityForAliases( ICollection logonNames, string tenantId, params string[] domains) @@ -96,7 +100,7 @@ private IDictionary GetIdentityForAliases( return identities; } - private IDictionary> CreatePossibleIdentityDescriptors( + private Dictionary> CreatePossibleIdentityDescriptors( IEnumerable aliases, string[] domains, string tenantId) @@ -119,14 +123,14 @@ private IDictionary> CreatePossibleIden return descriptors; } - private IDictionary GetIdentitiesForAliases( + private Dictionary GetIdentitiesForAliases( IDictionary> aliasDescriptors) { var descriptors = aliasDescriptors.SelectMany(ad => ad.Value).ToList(); var descriptorToAliasLookup = aliasDescriptors .SelectMany(ad => ad.Value.Select(d => new KeyValuePair(d.ToString(), ad.Key))) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase); - var validIdentities = new Dictionary(StringComparer.OrdinalIgnoreCase); + var validIdentities = new Dictionary(StringComparer.OrdinalIgnoreCase); var lookupResults = _identityManagementService.ReadIdentities(descriptors); foreach (var identity in lookupResults.Where(id => id != null)) { diff --git a/src/Qwiq.Identity/IdentityFieldValueConverter.cs b/src/Qwiq.Identity/IdentityFieldValueConverter.cs new file mode 100644 index 00000000..bb9bb893 --- /dev/null +++ b/src/Qwiq.Identity/IdentityFieldValueConverter.cs @@ -0,0 +1,50 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq.Identity +{ + public class IdentityFieldValueConverter : IdentityValueConverterBase + { + private static readonly IReadOnlyDictionary Empty = new Dictionary(); + [NotNull] private readonly IIdentityManagementService _identityManagementService; + + public IdentityFieldValueConverter( + [NotNull] IIdentityManagementService identityManagementService) + { + _identityManagementService = identityManagementService ?? throw new ArgumentNullException(nameof(identityManagementService)); + } + + + public override IReadOnlyDictionary Map(IEnumerable values) + { + if (values == null) return Empty; + + var identities = _identityManagementService.ReadIdentities(IdentitySearchFactor.DisplayName, values); + var retval = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var identity in identities) + { + var c = identity.Value?.Count(); + if (c.GetValueOrDefault(0) == 0) + { + retval.Add(identity.Key, new IdentityFieldValue(identity.Key)); + continue; + } + + if (c > 1) + { + var m = + $"Multiple identities found matching '{identity.Key}'. Please specify one of the following identities:{string.Join("\r\n- ", identity.Value)}"; + + throw new MultipleIdentitiesFoundException(m); + } + + var v = new IdentityFieldValue(identity.Value.FirstOrDefault()); + retval.Add(identity.Key, v); + } + + return retval; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity/IdentityValueConverterBase.cs b/src/Qwiq.Identity/IdentityValueConverterBase.cs new file mode 100644 index 00000000..fe10d74f --- /dev/null +++ b/src/Qwiq.Identity/IdentityValueConverterBase.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Qwiq.Identity +{ + public abstract class IdentityValueConverterBase : IIdentityValueConverter + { + public virtual object Map(string value) + { + if (string.IsNullOrWhiteSpace(value)) return value; + + var r = Map(new[] {value}); + if (r != null) + { + var kvp = r.FirstOrDefault(); + if (!EqualityComparer>.Default.Equals(kvp, default(KeyValuePair))) + { + if (kvp.Value != null) + { + return kvp.Value; + } + } + } + + return value; + } + public abstract IReadOnlyDictionary Map(IEnumerable values); + + [Obsolete("This method is depreciated and will be removed in a future version.")] + public virtual object Map(object value) + { + if (value is string stringValue) return Map(stringValue); + if (value is IEnumerable stringArray) + { + return Map(stringArray).Select(s=>s.Value).ToArray(); + } + + return value; + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity/MultipleIdentitiesFoundException.cs b/src/Qwiq.Identity/MultipleIdentitiesFoundException.cs new file mode 100644 index 00000000..f6835744 --- /dev/null +++ b/src/Qwiq.Identity/MultipleIdentitiesFoundException.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.Serialization; + +namespace Microsoft.Qwiq.Identity +{ + [Serializable] + public class MultipleIdentitiesFoundException : ApplicationException + { + public MultipleIdentitiesFoundException(string message) + : base(message) + { + } + + public MultipleIdentitiesFoundException() : base() + { + } + + public MultipleIdentitiesFoundException(string message, Exception innerException) : base(message, innerException) + { + } + + protected MultipleIdentitiesFoundException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Qwiq.Identity/Qwiq.Identity.csproj b/src/Qwiq.Identity/Qwiq.Identity.csproj index cb8bfb29..410b807f 100644 --- a/src/Qwiq.Identity/Qwiq.Identity.csproj +++ b/src/Qwiq.Identity/Qwiq.Identity.csproj @@ -33,8 +33,11 @@ + + + diff --git a/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs b/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs index f2e7d292..4882c8a0 100644 --- a/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs +++ b/src/Qwiq.Linq.Identity/Visitors/IdentityMappingVisitor.cs @@ -14,14 +14,14 @@ namespace Microsoft.Qwiq.Linq.Visitors public class IdentityMappingVisitor : IdentityComboStringVisitor { [NotNull] - private readonly IIdentityValueConverter _valueConverter; + private readonly IIdentityValueConverter _valueConverter; /// /// Initializes a new instance of the class. /// /// An instance of used to convert identity values. /// valueConverter - public IdentityMappingVisitor([NotNull] IIdentityValueConverter valueConverter) + public IdentityMappingVisitor([NotNull] IIdentityValueConverter valueConverter) { _valueConverter = valueConverter ?? throw new ArgumentNullException(nameof(valueConverter)); } @@ -35,7 +35,7 @@ protected override Expression VisitConstant(ConstantExpression node) { if (!NeedsIdentityMapping) return base.VisitConstant(node); - var newNode = _valueConverter.Map(node.Value); + var newNode = _valueConverter.Map(node.Value as string); return Expression.Constant(newNode); } } diff --git a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs index 7a6d065c..fe7b16b4 100644 --- a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs @@ -1,11 +1,12 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; using FastMember; - +using JetBrains.Annotations; using Microsoft.Qwiq.Identity; using Microsoft.Qwiq.Mapper.Attributes; @@ -18,7 +19,8 @@ namespace Microsoft.Qwiq.Mapper public class BulkIdentityAwareAttributeMapperStrategy : WorkItemMapperStrategyBase { private readonly IPropertyInspector _inspector; - private readonly IIdentityValueConverter _displayNameToAliasValueConverter; + private readonly IIdentityValueConverter _displayNameToAliasValueConverter; + private static readonly Hashtable IdentityPropertyTypeMap = new Hashtable(); /// /// Initializes a new instance of the class. @@ -31,7 +33,7 @@ public class BulkIdentityAwareAttributeMapperStrategy : WorkItemMapperStrategyBa /// identityManagementService /// public BulkIdentityAwareAttributeMapperStrategy(IPropertyInspector inspector, IIdentityManagementService identityManagementService) - :this(inspector, new DisplayNameToAliasValueConverter(identityManagementService)) + : this(inspector, new IdentityFieldValueConverter(identityManagementService)) { } @@ -44,7 +46,7 @@ public BulkIdentityAwareAttributeMapperStrategy(IPropertyInspector inspector, II /// public BulkIdentityAwareAttributeMapperStrategy( IPropertyInspector inspector, - IIdentityValueConverter identityValueConverter + IIdentityValueConverter identityValueConverter ) { Contract.Requires(inspector != null); @@ -66,14 +68,11 @@ public override void Map(Type targeWorkItemType, IEnumerable kvp.Key, kvp => kvp.Value, WorkItemComparer.Default); var validIdentityProperties = GetWorkItemIdentityFieldNameToIdentityPropertyMap(targeWorkItemType, _inspector); - if (!validIdentityProperties.Any())return; - - var accessor = TypeAccessor.Create(targeWorkItemType, true); - var validIdentityFieldsWithWorkItems = GetWorkItemsWithIdentityFieldValues(workingSet.Keys, validIdentityProperties.Keys.ToList()); - var identitySearchTerms = GetIdentitySearchTerms(validIdentityFieldsWithWorkItems).ToList(); - - var identitySearchResults = (Dictionary)_displayNameToAliasValueConverter.Map(identitySearchTerms); + if (!validIdentityProperties.Any()) return; + var validIdentityFieldsWithWorkItems = GetWorkItemsWithIdentityFieldValues(workingSet.Keys, validIdentityProperties.Keys); + var identitySearchTerms = GetIdentitySearchTerms(validIdentityFieldsWithWorkItems); + var identitySearchResults = MapIdentityValues(identitySearchTerms); foreach (var workItem in validIdentityFieldsWithWorkItems) @@ -81,61 +80,163 @@ public override void Map(Type targeWorkItemType, IEnumerable MapIdentityValues(IEnumerable identitySearchTerms) + { + try + { + return _displayNameToAliasValueConverter.Map(identitySearchTerms); + } + catch (Exception e) + { + throw new AttributeMapException("Unable to map identity values.", e); + } + } - private static ICollection GetWorkItemsWithIdentityFieldValues( + private static List GetWorkItemsWithIdentityFieldValues( IEnumerable workItems, - IReadOnlyCollection witFieldNames) + Dictionary>.KeyCollection witFieldNames) { - return - workItems.Select( - wi => - new WorkItemWithFields - { - WorkItem = wi, - ValidFields = - witFieldNames - .Where(fn => wi.Fields.Contains(fn)) - .Select(fn => - new WorkItemField - { - Name = fn, - Value = wi[fn] as string - }) - .Where(f => !string.IsNullOrEmpty(f.Value)) - }).ToList(); + var retval = new List(); + foreach (var wi in workItems) + { + var vf = new List(witFieldNames.Count); + foreach (var fn in witFieldNames) + { + if (!wi.Fields.Contains(fn)) continue; + var fv = wi[fn] as string; + + if (string.IsNullOrEmpty(fv)) continue; + vf.Add(new WorkItemField { Name = fn, Value = fv }); + } + + retval.Add(new WorkItemWithFields + { + WorkItem = wi, + ValidFields = vf + }); + } + return retval; } - internal static IEnumerable GetIdentitySearchTerms(ICollection workItemsWithIdentityFields) + internal static HashSet GetIdentitySearchTerms(List workItemsWithIdentityFields) { - return workItemsWithIdentityFields.SelectMany(wiwf => wiwf.ValidFields.Select(f => f.Value)).Distinct(); + var set = new HashSet(); + foreach (var wiwf in workItemsWithIdentityFields) + { + foreach (var f in wiwf.ValidFields) + { + set.Add(f.Value); + } + } + + return set; } - internal static IDictionary GetWorkItemIdentityFieldNameToIdentityPropertyMap(Type targetWorkItemType, IPropertyInspector propertyInspector) + internal static Dictionary> GetWorkItemIdentityFieldNameToIdentityPropertyMap(Type targetWorkItemType, IPropertyInspector propertyInspector) { - var identityProperties = propertyInspector.GetAnnotatedProperties(targetWorkItemType, typeof(IdentityFieldAttribute)); - return - identityProperties - .Select( - p => - new - { - IdentityProperty = p, - WitFieldName = propertyInspector.GetAttribute(p)?.FieldName - }) - .Where(p => !string.IsNullOrEmpty(p.WitFieldName) && p.IdentityProperty.CanWrite) - .ToDictionary(x => x.WitFieldName, x => x.IdentityProperty); + var hashtable = IdentityPropertyTypeMap; + var props = (Dictionary>)hashtable[targetWorkItemType]; + + if (props != null) return props; + + lock (hashtable) + { + + var identityProperties = + propertyInspector.GetAnnotatedProperties(targetWorkItemType, typeof(IdentityFieldAttribute)); + props = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var identityProperty in identityProperties) + { + var witFieldName = propertyInspector.GetAttribute(identityProperty) + ?.FieldName; + if (string.IsNullOrEmpty(witFieldName) || !identityProperty.CanWrite) continue; + + if (!props.ContainsKey(witFieldName)) props.Add(witFieldName, new List()); + props[witFieldName].Add(identityProperty); + } + + hashtable[targetWorkItemType] = props; + return props; + } } } } diff --git a/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs b/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs index b5e70e9d..fb18ea55 100644 --- a/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs +++ b/src/Qwiq.Mapper.Identity/IdentityFieldAttributeVisitor.cs @@ -18,13 +18,13 @@ namespace Microsoft.Qwiq.Linq.Visitors public class IdentityFieldAttributeVisitor : ExpressionVisitor { [NotNull] - private readonly IIdentityValueConverter _valueConverter; + private readonly IIdentityValueConverter _valueConverter; /// /// Initializes a new instance of the class. /// - /// An instance of used to convert identity values. - public IdentityFieldAttributeVisitor([NotNull] IIdentityValueConverter valueConverter) + /// An instance of IIdentityValueConverter used to convert identity values. + public IdentityFieldAttributeVisitor([NotNull] IIdentityValueConverter valueConverter) { Contract.Requires(valueConverter != null); @@ -57,7 +57,7 @@ protected override Expression VisitConstant(ConstantExpression node) { if (!NeedsIdentityMapping) return base.VisitConstant(node); - var newNode = _valueConverter.Map(node.Value); + var newNode = _valueConverter.Map(node.Value as string); return Expression.Constant(newNode); } diff --git a/src/Qwiq.Mapper.Identity/WorkItemWithFields.cs b/src/Qwiq.Mapper.Identity/WorkItemWithFields.cs index f3f0eee7..3da72c6f 100644 --- a/src/Qwiq.Mapper.Identity/WorkItemWithFields.cs +++ b/src/Qwiq.Mapper.Identity/WorkItemWithFields.cs @@ -7,6 +7,6 @@ namespace Microsoft.Qwiq.Mapper internal struct WorkItemWithFields { public IWorkItem WorkItem { get; set; } - public IEnumerable ValidFields { get; set; } + public List ValidFields { get; set; } } } diff --git a/src/Qwiq.Mapper/AttributeMapException.cs b/src/Qwiq.Mapper/AttributeMapException.cs index 34254604..25e7d6cc 100644 --- a/src/Qwiq.Mapper/AttributeMapException.cs +++ b/src/Qwiq.Mapper/AttributeMapException.cs @@ -1,22 +1,82 @@ -using System; +using JetBrains.Annotations; +using System; +using System.Diagnostics; +using System.Reflection; namespace Microsoft.Qwiq.Mapper { - public class AttributeMapException : Exception + [DebuggerDisplay("{SourceField} -> {DestinationProperty.Name}")] + public struct PropertyMap { + public PropertyMap(PropertyInfo destinationProperty, string sourceField) + { + DestinationProperty = destinationProperty; + SourceField = sourceField; + } + + public PropertyInfo DestinationProperty { get; } + public string SourceField { get; } + } + + [DebuggerDisplay("{Source.WorkItemType} -> {Destination.Name}")] + public struct TypePair + { + public TypePair(IWorkItem source, Type destination) + { + Source = source; + Destination = destination; + } + + public Type Destination { get; } + public IWorkItem Source { get; } + } + + public class AttributeMapException : ApplicationException + { + private readonly string _message; + public AttributeMapException() { } - public AttributeMapException(string message) - :base(message) + public AttributeMapException([CanBeNull] string message) + : base(message) => _message = message; + + public AttributeMapException([CanBeNull] string message, [CanBeNull] Exception innerException) + : base(message, innerException) => _message = message; + + public AttributeMapException([CanBeNull] string message, [CanBeNull] Exception innerException, TypePair typePair, PropertyMap properties) + + : this(message, innerException) { + Types = typePair; + PropertyMap = properties; } - public AttributeMapException(string message, Exception inner) - :base(message, inner) + public override string Message { + get + { + var message = _message ?? string.Empty; + var newLine = Environment.NewLine; + + if (Types?.Source != null && Types?.Destination != null) + { + message = $"{message}{newLine}{newLine}Mapping types:"; + message += $"{newLine}{Types?.Source.WorkItemType} -> {Types?.Destination.FullName}"; + } + if (PropertyMap?.DestinationProperty != null && !string.IsNullOrEmpty(PropertyMap?.SourceField)) + { + message = $"{message}{newLine}{newLine}Property:"; + message += $"{newLine}{PropertyMap?.SourceField} -> {PropertyMap?.DestinationProperty.Name}"; + } + + return message; + } } + + public PropertyMap? PropertyMap { get; set; } + public TypePair? Types { get; set; } } -} +} \ No newline at end of file diff --git a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs index 98cf3d65..2fcb881a 100644 --- a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs +++ b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs @@ -103,7 +103,9 @@ public override void Given() IdentityFieldBackingValue = identityDisplay; Identities = new Dictionary> { +#pragma warning disable 0618 {IdentityFieldBackingValue, new []{new MockTeamFoundationIdentity(identityDisplay, identityAlias) }} +#pragma warning restore 0618 }; base.Given(); } @@ -119,22 +121,30 @@ public void set_on_an_identity_property_should_be_called_once() { Actual.AnIdentitySetCount.ShouldEqual(1); } + + [TestMethod] + public void the_IdentityFieldValue_contains_expected_value() + { + Actual.AnIdentityValue.ShouldNotBeNull(); + Actual.AnIdentityValue.DisplayName.ShouldEqual(identityDisplay); + Actual.AnIdentityValue.IdentityName.ShouldEqual(identityAlias); + } } [TestClass] public class given_a_work_item_with_defined_fields_when_the_field_names_to_properties_are_retrieved : ContextSpecification { private readonly Type _identityType = typeof(MockIdentityType); - private IDictionary Expected { get; set; } - private IDictionary Actual { get; set; } + private Dictionary> Expected { get; set; } + private Dictionary> Actual { get; set; } public override void Given() { - Expected = new Dictionary + Expected = new Dictionary> { - { MockIdentityType.BackingField, _identityType.GetProperty("AnIdentity") }, - { MockIdentityType.NonExistantField, _identityType.GetProperty("NonExistant") }, - { MockIdentityType.UriIdentityField, _identityType.GetProperty("UriIdentity") } + [MockIdentityType.BackingField] = new List { _identityType.GetProperty(nameof(MockIdentityType.AnIdentity)), _identityType.GetProperty(nameof(MockIdentityType.AnIdentityValue)) }, + [MockIdentityType.NonExistantField] = new List { _identityType.GetProperty(nameof(MockIdentityType.NonExistant)) }, + [MockIdentityType.UriIdentityField] = new List { _identityType.GetProperty(nameof(MockIdentityType.UriIdentity)) } }; } @@ -149,7 +159,10 @@ public override void When() [TestMethod] public void only_valid_fields_and_properties_are_retrieved() { - Actual.ShouldContainOnly(Expected); + Actual.Count.ShouldEqual(Expected.Count); + Actual[MockIdentityType.BackingField].ShouldContainOnly(Expected[MockIdentityType.BackingField]); + Actual[MockIdentityType.NonExistantField].ShouldContainOnly(Expected[MockIdentityType.NonExistantField]); + Actual[MockIdentityType.UriIdentityField].ShouldContainOnly(Expected[MockIdentityType.UriIdentityField]); } } diff --git a/test/Qwiq.Identity.Tests/IdentityMapperTests.cs b/test/Qwiq.Identity.Tests/IdentityMapperTests.cs index 004ddd76..1a4fb5c5 100644 --- a/test/Qwiq.Identity.Tests/IdentityMapperTests.cs +++ b/test/Qwiq.Identity.Tests/IdentityMapperTests.cs @@ -32,7 +32,9 @@ public override void Given() public override void When() { +#pragma warning disable 0618 var result = Instance.Map(Input); +#pragma warning restore 0618 Debug.Print("Result: " + result.ToUsefulString()); diff --git a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs index 3d28798a..19e2d51d 100644 --- a/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs +++ b/test/Qwiq.Identity.Tests/Mocks/IdentityMockType.cs @@ -28,6 +28,10 @@ public string AnIdentity } } + [FieldDefinition(BackingField)] + [IdentityField] + public IdentityFieldValue AnIdentityValue { get;set;} + [IdentityField] public string NoBacking { get; set; } diff --git a/test/Qwiq.Integration.Tests/Identity/Soap/SoapIdentityMapperContextSpecification.cs b/test/Qwiq.Integration.Tests/Identity/Soap/SoapIdentityMapperContextSpecification.cs index 004c07b4..6c577495 100644 --- a/test/Qwiq.Integration.Tests/Identity/Soap/SoapIdentityMapperContextSpecification.cs +++ b/test/Qwiq.Integration.Tests/Identity/Soap/SoapIdentityMapperContextSpecification.cs @@ -27,7 +27,9 @@ public override void Given() public override void When() { +#pragma warning disable 0618 ActualOutput = TimedAction(() => (T)Instance.Map(Input), "SOAP", "Map"); +#pragma warning restore 0618 } [TestMethod] From 767ceb837d7d7eb05a2ae012abe166320a2aa968 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 17:58:30 -0700 Subject: [PATCH 245/251] Improved mapping exception When an exception is encountered, a more helpful message is sent: ``` Unable to convert/set [identity] field value on 123456. Mapping types: Customer Promise -> Namespace.Models.CustomerPromise Property: Owner -> Owner ``` The `InnerException` of the exception will be set with the actual exception. --- .../Attributes/AttributeMapperStrategy.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs index 74c35e8c..7e4192cd 100644 --- a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs @@ -88,7 +88,6 @@ protected internal virtual void AssignFieldValue( return; } - var sourceType = fieldValue.GetType(); if (convert) { try @@ -97,12 +96,25 @@ protected internal virtual void AssignFieldValue( } catch (Exception e) { - throw new AttributeMapException($"Unable to convert field {fieldName} on work item {sourceWorkItem.Id}: {sourceWorkItem.WorkItemType} to property {property.Name} on {targetWorkItemType}.", e); + var tm = new TypePair(sourceWorkItem, targetWorkItemType); + var pm = new PropertyMap(property, fieldName); + var message = $"Unable to convert field value on {sourceWorkItem.Id}."; + throw new AttributeMapException(message, e, tm, pm); } } var accessor = TypeAccessor.Create(targetWorkItemType, true); - accessor[targetWorkItem, property.Name] = fieldValue; + try + { + accessor[targetWorkItem, property.Name] = fieldValue; + } + catch (Exception e) + { + var tm = new TypePair(sourceWorkItem, targetWorkItemType); + var pm = new PropertyMap(property, fieldName); + var message = $"Unable to set field value on {sourceWorkItem.Id}."; + throw new AttributeMapException(message, e, tm, pm); + } } protected internal virtual void MapImpl(Type targetWorkItemType, IWorkItem sourceWorkItem, object targetWorkItem) From ed31c2c244745640c21f39de6d238b161b3c79ab Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 17:59:42 -0700 Subject: [PATCH 246/251] TypeParser: Performance improvements Cache `TypeConverter` for specific type to avoid many reflection calls in mapping operations. --- src/Qwiq.Core/TypeParser.cs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Core/TypeParser.cs b/src/Qwiq.Core/TypeParser.cs index d90f1d41..83ae65ae 100644 --- a/src/Qwiq.Core/TypeParser.cs +++ b/src/Qwiq.Core/TypeParser.cs @@ -1,5 +1,6 @@ using JetBrains.Annotations; using System; +using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -11,6 +12,8 @@ namespace Microsoft.Qwiq /// public class TypeParser : ITypeParser { + private static readonly Hashtable TypeConverters = new Hashtable(); + private TypeParser() { } @@ -211,7 +214,8 @@ private static bool TryConvert(Type destinationType, object value, out object re } var valueType = value.GetType(); - var typeConverter = TypeDescriptor.GetConverter(valueType); + var typeConverter = GetTypeConverter(valueType); + if (typeConverter.CanConvertTo(destinationType)) try { @@ -224,7 +228,7 @@ private static bool TryConvert(Type destinationType, object value, out object re { } - typeConverter = TypeDescriptor.GetConverter(destinationType); + typeConverter = GetTypeConverter(destinationType); if (typeConverter.CanConvertFrom(valueType)) try { @@ -258,6 +262,22 @@ private static bool TryConvert(Type destinationType, object value, out object re return false; } + [MustUseReturnValue] + private static TypeConverter GetTypeConverter([NotNull] Type valueType) + { + var hashtable = TypeConverters; + + var typeConverter = (TypeConverter) hashtable[valueType]; + if (typeConverter != null) return typeConverter; + + lock (hashtable) + { + typeConverter = TypeDescriptor.GetConverter(valueType); + hashtable[valueType] = typeConverter; + } + return typeConverter; + } + [ContractAnnotation("value:null => true")] private static bool ValueRepresentsNull([CanBeNull] object value) { From e39f7b51c709024dde0134bde888fcfb5b91283f Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 18:00:55 -0700 Subject: [PATCH 247/251] Corrections to IdentityFieldValue operator and ToString - Convert string conversion operator to implicit - Have operator use `ToString()` value - Update ToString to use IdentityName if available; otherwise, DisplayName --- src/Qwiq.Core/IdentityFieldValue.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Qwiq.Core/IdentityFieldValue.cs b/src/Qwiq.Core/IdentityFieldValue.cs index 1653edf5..e8eff624 100644 --- a/src/Qwiq.Core/IdentityFieldValue.cs +++ b/src/Qwiq.Core/IdentityFieldValue.cs @@ -40,7 +40,7 @@ public IdentityFieldValue([NotNull] ITeamFoundationIdentity identity) : this(identity.DisplayName, identity.Descriptor?.Identifier, identity.TeamFoundationId.ToString()) { Contract.Requires(identity != null); - + if (identity == null) throw new ArgumentNullException(nameof(identity)); } @@ -196,14 +196,13 @@ public string IdentityName public string TeamFoundationId { get; } /// - /// Performs an explicit conversion from to . + /// Performs an implicit conversion from to . /// /// The value. /// If is null, null; otherwise, . - public static explicit operator string(IdentityFieldValue value) + public static implicit operator string(IdentityFieldValue value) { - if (value == null) return null; - return value.IdentityName; + return value?.ToString(); } /// @@ -211,7 +210,7 @@ public override string ToString() { return string.IsNullOrEmpty(IdentityName) ? DisplayName - : $"{DisplayName} <{AccountName}>".ToString(CultureInfo.InvariantCulture); + : IdentityName; } private static bool TryGetAccountName(string search, out string acccountName) From 2ad7ec35c41f469e77b0977c2927a7bc9b08cb8f Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 18:01:18 -0700 Subject: [PATCH 248/251] Mark lossy method of creating Mock TFS identities as Obsolete --- test/Qwiq.Mocks/MockTeamFoundationIdentity.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs b/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs index 5758700b..daac88f1 100644 --- a/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs +++ b/test/Qwiq.Mocks/MockTeamFoundationIdentity.cs @@ -40,6 +40,7 @@ public MockTeamFoundationIdentity( }; } + [Obsolete("This method has been deprecated and will be removed in a future release.")] public MockTeamFoundationIdentity(string displayName, string uniqueName) : this(MockIdentityDescriptor.Create(uniqueName), displayName, Guid.Empty) { From 9a76c78d13d66f9c55d18e3c9fc91c41b9876faf Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 18:47:15 -0700 Subject: [PATCH 249/251] Performance improvements: Use dictionary instead of enumerable kvp Bulk identity mapping operations take [6.3145 us; 6.4727 us] (CI 99.9%) with memory-backed IMS --- .../BulkIdentityAwareAttributeMapperStrategy.cs | 8 +++----- src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs | 4 ++-- src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs | 2 +- src/Qwiq.Mapper/IWorkItemMapperStrategy.cs | 4 ++-- src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs | 8 ++++---- test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs | 4 ++-- .../BulkIdentityAwareAttributeMapperStrategyTests.cs | 4 ++-- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs index fe7b16b4..64cdd981 100644 --- a/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper.Identity/BulkIdentityAwareAttributeMapperStrategy.cs @@ -62,22 +62,20 @@ IIdentityValueConverter identityValueConverter /// Type of the targe work item. /// The work item mappings. /// The work item mapper. - public override void Map(Type targeWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) + public override void Map(Type targeWorkItemType, IDictionary> workItemMappings, IWorkItemMapper workItemMapper) { if (!workItemMappings.Any()) return; - var workingSet = workItemMappings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, WorkItemComparer.Default); var validIdentityProperties = GetWorkItemIdentityFieldNameToIdentityPropertyMap(targeWorkItemType, _inspector); if (!validIdentityProperties.Any()) return; - var validIdentityFieldsWithWorkItems = GetWorkItemsWithIdentityFieldValues(workingSet.Keys, validIdentityProperties.Keys); + var validIdentityFieldsWithWorkItems = GetWorkItemsWithIdentityFieldValues(workItemMappings.Keys, validIdentityProperties.Keys); var identitySearchTerms = GetIdentitySearchTerms(validIdentityFieldsWithWorkItems); var identitySearchResults = MapIdentityValues(identitySearchTerms); - foreach (var workItem in validIdentityFieldsWithWorkItems) { - var targetObject = workingSet[workItem.WorkItem]; + var targetObject = workItemMappings[workItem.WorkItem]; foreach (var sourceField in workItem.ValidFields) { var targetProperties = validIdentityProperties[sourceField.Name]; diff --git a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs index 7e4192cd..3865be94 100644 --- a/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/AttributeMapperStrategy.cs @@ -28,7 +28,7 @@ public AttributeMapperStrategy([NotNull] IPropertyInspector inspector, [NotNull] _inspector = inspector ?? throw new ArgumentNullException(nameof(inspector)); } - public override void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) + public override void Map(IDictionary workItemMappings, IWorkItemMapper workItemMapper) { var targetWorkItemType = typeof(T); @@ -41,7 +41,7 @@ public override void Map(IEnumerable> workItemMapp } } - public override void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) + public override void Map(Type targetWorkItemType, IDictionary> workItemMappings, IWorkItemMapper workItemMapper) { foreach (var workItemMapping in workItemMappings) { diff --git a/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs b/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs index 1b00c476..d7f474fd 100644 --- a/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs +++ b/src/Qwiq.Mapper/Attributes/WorkItemLinksMapperStrategy.cs @@ -58,7 +58,7 @@ protected virtual IEnumerable Query(IEnumerable ids) return Store.Query(ids); } - public override void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper) + public override void Map(Type targetWorkItemType, IDictionary> workItemMappings, IWorkItemMapper workItemMapper) { var linksLookup = BuildLinksRelationships(targetWorkItemType, workItemMappings); diff --git a/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs b/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs index 0c135a22..94c9931d 100644 --- a/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs +++ b/src/Qwiq.Mapper/IWorkItemMapperStrategy.cs @@ -5,7 +5,7 @@ namespace Microsoft.Qwiq.Mapper { public interface IWorkItemMapperStrategy { - void Map(Type targetWorkItemType, IEnumerable>> workItemMappings, IWorkItemMapper workItemMapper); - void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) where T : IIdentifiable, new(); + void Map(Type targetWorkItemType, IDictionary> workItemMappings, IWorkItemMapper workItemMapper); + void Map(IDictionary workItemMappings, IWorkItemMapper workItemMapper) where T : IIdentifiable, new(); } } diff --git a/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs b/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs index 8203c88b..1e33d10d 100644 --- a/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs +++ b/src/Qwiq.Mapper/WorkItemMapperStrategyBase.cs @@ -8,17 +8,17 @@ public abstract class WorkItemMapperStrategyBase : IWorkItemMapperStrategy { public virtual void Map( Type targetWorkItemType, - IEnumerable>> workItemMappings, + IDictionary> workItemMappings, IWorkItemMapper workItemMapper) { foreach (var workItemMapping in workItemMappings) Map(targetWorkItemType, workItemMapping.Key, workItemMapping.Value, workItemMapper); } - public virtual void Map(IEnumerable> workItemMappings, IWorkItemMapper workItemMapper) + public virtual void Map(IDictionary workItemMappings, IWorkItemMapper workItemMapper) where T : IIdentifiable, new() { - Map(typeof(T), workItemMappings.Select(s => new KeyValuePair>(s.Key, s.Value)), workItemMapper); + Map(typeof(T), workItemMappings.ToDictionary(k => k.Key, e => (IIdentifiable)e.Value, Comparer.WorkItem), workItemMapper); } protected virtual void Map( @@ -27,7 +27,7 @@ protected virtual void Map( IIdentifiable targetWorkItem, IWorkItemMapper workItemMapper) { - Map(targetWorkItemType, new[] { new KeyValuePair>(sourceWorkItem, targetWorkItem) }, workItemMapper); + Map(targetWorkItemType, new Dictionary>(Comparer.WorkItem) { { sourceWorkItem, targetWorkItem } }, workItemMapper); } protected virtual void Map(IWorkItem sourceWorkItem, T targetWorkItem, IWorkItemMapper workItemMapper) diff --git a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs index 92657787..23bbbc3a 100644 --- a/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs +++ b/test/Qwiq.Identity.Benchmark.Tests/Benchmark.cs @@ -37,7 +37,7 @@ public void Execute_Identity_Mapping_Performance_Benchmark() public class Benchmark { private IWorkItemMapperStrategy _strategy; - private IEnumerable>> _workItemMappings; + private Dictionary> _workItemMappings; [Setup] public void SetupData() @@ -52,7 +52,7 @@ public void SetupData() var generator = new WorkItemGenerator(() => wis.Create(), new[] { "Revisions", "Item" }); wis.Add(generator.Generate()); - _workItemMappings = generator.Items.Select(t => new KeyValuePair>(t, new MockIdentityType())).ToList(); + _workItemMappings = generator.Items.ToDictionary(k => (IWorkItem) k, e => (IIdentifiable) new MockIdentityType()); } diff --git a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs index 2fcb881a..b85cb949 100644 --- a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs +++ b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.Qwiq.Identity public abstract class BulkIdentityAwareAttributeMapperStrategyTests : ContextSpecification { private IWorkItemMapperStrategy _strategy; - private IEnumerable>> _workItemMappings; + private Dictionary> _workItemMappings; protected IDictionary> Identities { get; set; } protected MockIdentityType Actual @@ -45,7 +45,7 @@ public override void Given() }) }; - _workItemMappings = sourceWorkItems.Select(t => new KeyValuePair>(t, new MockIdentityType())).ToList(); + _workItemMappings = sourceWorkItems.ToDictionary(k => (IWorkItem)k, e => (IIdentifiable)new MockIdentityType()); } public override void When() From b1a472ac7d020b35a1d04128b09fac963d85a083 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 19:33:00 -0700 Subject: [PATCH 250/251] Move to yield for mapper to avoid memory allocation --- src/Qwiq.Mapper/WorkItemMapper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Qwiq.Mapper/WorkItemMapper.cs b/src/Qwiq.Mapper/WorkItemMapper.cs index 2284f239..645dc01f 100644 --- a/src/Qwiq.Mapper/WorkItemMapper.cs +++ b/src/Qwiq.Mapper/WorkItemMapper.cs @@ -30,7 +30,7 @@ public WorkItemMapper([NotNull] IEnumerable mapperStrat public IEnumerable Create(IEnumerable collection) where T : IIdentifiable, new() { - var workItemsToMap = new Dictionary(); + var workItemsToMap = new Dictionary(Comparer.WorkItem); foreach (var item in collection) { workItemsToMap[item] = new T(); @@ -41,7 +41,10 @@ public WorkItemMapper([NotNull] IEnumerable mapperStrat strategy.Map(workItemsToMap, this); } - return workItemsToMap.Select(wi => wi.Value); + foreach (var wi in workItemsToMap) + { + yield return wi.Value; + } } public IEnumerable> Create(Type type, IEnumerable collection) From 134b949dc20a0ba2661ceab020d7883c76a4a9a0 Mon Sep 17 00:00:00 2001 From: Richard Murillo Date: Mon, 12 Jun 2017 19:34:12 -0700 Subject: [PATCH 251/251] Remove test that asserts null when ident not mapped This assertion goes against the normalitive behavior of other mappers: when the value cannot be mapped, the same value is returned. --- ...entityAwareAttributeMapperStrategyTests.cs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs index b85cb949..b897c235 100644 --- a/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs +++ b/test/Qwiq.Identity.Tests/BulkIdentityAwareAttributeMapperStrategyTests.cs @@ -70,29 +70,6 @@ public void the_actual_identity_value_should_be_null() } } - [TestClass] - public class when_the_backing_source_does_not_result_in_a_resolved_identity : BulkIdentityAwareAttributeMapperStrategyTests - { - public override void Given() - { - IdentityFieldBackingValue = "Anon Existant"; - Identities = new Dictionary>(); - base.Given(); - } - - [TestMethod] - public void the_actual_identity_value_should_be_null() - { - Actual.AnIdentity.ShouldBeNull(); - } - - [TestMethod] - public void set_on_an_identity_property_should_not_be_called() - { - Actual.AnIdentitySetCount.ShouldEqual(0); - } - } - [TestClass] public class when_the_backing_source_does_result_in_a_resolved_identity : BulkIdentityAwareAttributeMapperStrategyTests {