Skip to content

Improve OData and validation test coverage#324

Open
rgregg-msft wants to merge 13 commits intomasterfrom
improve-odata-test-coverage
Open

Improve OData and validation test coverage#324
rgregg-msft wants to merge 13 commits intomasterfrom
improve-odata-test-coverage

Conversation

@rgregg-msft
Copy link
Contributor

Summary

  • Fixes a static cache bug in ODataParser.BuildJsonExample where visitedProperties and generatedExamples were process-level singletons, causing stale results across calls
  • Adds 196 tests across 7 new test files covering OData parsing, navigation, extension methods, action/function substitution, enum types, the transformation pipeline, schema validation, and string utilities
  • Overall coverage of ApiDoctor.Validation.OData substantially improved (EntityType 88.8%, EntityContainer 92.5%, ODataCollection 94.7%, Schema 95.2%)

New test files

File Tests Coverage target
ODataParserTests.cs 32 Deserialization, BuildJsonExample, resource generation, cache regression
ODataNavigationTests.cs 27 NavigateByUriComponent on EntityType, ComplexType, EntityContainer, ODataCollection
ODataExtensionMethodTests.cs 15 NamespaceOnly, TypeOnly, HasNamespace, IsCollection, ResourceWithIdentifier
ActionOrFunctionTests.cs 23 CanSubstituteFor contravariance, parameter matching, deserialization
EnumTypeTests.cs 10 Deserialization, IsFlags, NavigateByUriComponent / NavigateByEntityTypeKey
TransformationHelperTests.cs 19 ApplyTransformation, ApplyTransformationToCollection (wildcard, Remove, version filtering, Add)
SchemaValidationTests.cs 6 ValidateSchemaTypes for valid/invalid type references
StringSuggestionsAndXmlParseHelperTests.cs 16 Damerau-Levenshtein matching, XmlParseHelper element name validation

Test plan

  • All 196 tests pass locally (dotnet test)
  • CI runs on push (Windows + Ubuntu matrix)
  • No regressions against existing tests

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 17, 2026 21:28
rgregg-msft and others added 8 commits March 17, 2026 14:32
…resource generation

Adds 30 tests covering ODataParser.DeserializeEntityFramework (standard types,
ags: custom attributes, inheritance merging), BuildJsonExample (primitives,
collections, nested complex types, Edm.Stream exclusion), GenerateResourcesFromSchemas
(namespace validation, alias namespace, multi-schema), and serialize/deserialize
round-trip preservation of all ags: attributes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 42 tests covering the surface area that directly gates the ODataLib
migration decision:

ODataNavigationTests (27 tests):
- EntityType.NavigateByUriComponent: collection nav property, scalar nav
  property, unknown component, case-mismatch error, base type fallback,
  property on self
- ComplexType.NavigateByUriComponent: property match, unknown component,
  case-mismatch error, base type fallback
- EntityContainer.NavigateByUriComponent: EntitySet returns ODataCollection,
  Singleton returns EntityType, unknown returns null, key throws
- ODataCollection.NavigateByUriComponent: Guid key, long key, type cast
  segment, unknown segment, NavigateByEntityTypeKey

ODataExtensionMethodTests (15 tests):
- NamespaceOnly: qualified, single-segment, collection, no-namespace throws
- TypeOnly: qualified, collection, unqualified passthrough
- HasNamespace: qualified true, unqualified false
- IsCollection: collection true, Edm false, qualified false
- ElementName: collection unwraps, non-collection passthrough, Edm collection
- LookupNavigableType: EntityType, ComplexType, Edm primitive, unknown throws
- ResourceWithIdentifier: EntityType, ODataSimpleType, Collection, unknown throws

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ActionOrFunctionTests (21 tests):
- CanSubstituteFor: identical functions, null other, different name/IsBound/
  param count/return type, both-null return type, one-null return type,
  Action vs Function type mismatch
- Parameter matching: name not found, IsNullable mismatch, Unicode mismatch,
  type mismatch with no inheritance
- Contravariance: this=base/other=derived returns true, reversed returns false,
  multi-level (grandparent to grandchild) returns true
- Deserialization: Function with parameters and ReturnType, IsComposable flag,
  Action without ReturnType

EnumTypeTests (10 tests):
- Deserialization: name and members, UnderlyingType, IsFlags, IsFlags defaults
  false, UnderlyingType null when absent, member without value, multiple enums,
  TypeIdentifier equals Name
- Navigation boundaries: NavigateByUriComponent and NavigateByEntityTypeKey
  both throw NotImplementedException

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes five gaps identified in adversarial review:

- BuildJsonExample: assert actual values (not just JTokenTypes) to catch stale
  static-cache hits; documents the shared-state risk in a comment
- GenerateResourcesFromSchemas: add test asserting enum types are intentionally
  excluded from resource output
- EntityType collection nav test: chain through NavigateByEntityTypeKey to
  verify the element type is actually resolvable in the edmx, not just that the
  identifier string is correct
- CanSubstituteFor: add two tests for null parameter type (this.Type=null and
  other.Type=null), exercising the null guard at ActionOrFunctionBase.cs:68-69
- Function deserialization: assert parameter types in addition to names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
visitedProperties and generatedExamples were static fields on ODataParser,
making them process-level singletons that accumulated state across calls.
This caused two problems:
  1. A second call to BuildJsonExample on the same type returned stale cached
     property values rather than regenerating them.
  2. Tests were order-dependent and could interfere if they used the same
     namespace+typename+property key.

The visited set and cache are now allocated fresh on each call to
BuildJsonExample and threaded down through BuildDictionaryExample,
ExampleOfType, and ObjectExampleForType as parameters. The recursion guard
still functions correctly for circular type references within a single call.

Adds two regression tests:
- CalledTwiceOnSameType_SecondCallGeneratesFreshResult: would have failed
  against the old code if the second call lost the property entirely
- CircularTypeReference_DoesNotStackOverflow: verifies the recursion guard
  still works correctly after the refactor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tring utilities

- TransformationHelperTests: 19 tests covering ApplyTransformation (simple copy,
  null skip, alternative handler intercept/pass-through, GraphPropertyName rename)
  and ApplyTransformationToCollection (exact match, wildcard, Remove, version
  filtering, Add with/without version match)
- SchemaValidationTests: 6 tests covering ValidateSchemaTypes for empty schema,
  primitives, known entity/complex type references, unknown type, cross-schema
  resolution, and collection types
- StringSuggestionsAndXmlParseHelperTests: 16 tests covering Damerau-Levenshtein
  fuzzy matching (exact, one-char diff, transposition, threshold) and XmlParseHelper
  element name validation

196 tests total, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add coverlet.runsettings: configures Cobertura output format and enforces a
  20% minimum line coverage threshold (Total). Build fails if coverage drops
  below this. Threshold set conservatively below the current ~24% baseline;
  raise it incrementally as coverage improves.
- Update dotnet.yml: pass --settings coverlet.runsettings to the coverage test
  step so the threshold is checked on every run
- Add ReportGenerator step: generates MarkdownSummaryGithub + HTML report from
  the Cobertura XML
- Post coverage summary to $GITHUB_STEP_SUMMARY so it appears in the Actions
  job summary on every PR and push (no extra permissions required)
- Upload HTML coverage report as a build artifact for detailed drill-down

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add pull-requests: write permission to the build job
- Add marocchino/sticky-pull-request-comment step that posts the ReportGenerator
  markdown summary directly on the PR, updating in place on each push rather
  than creating duplicate comments
- Comment is only posted on pull_request events (not push to master)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rgregg-msft rgregg-msft force-pushed the improve-odata-test-coverage branch from 326e5ab to a55deb8 Compare March 17, 2026 21:32
@github-actions
Copy link

github-actions bot commented Mar 17, 2026

Summary

Summary
Generated on: 03/17/2026 - 21:45:07
Coverage date: 03/17/2026 - 21:44:58 - 03/17/2026 - 21:45:04
Parser: MultiReport (3x Cobertura)
Assemblies: 4
Classes: 230
Files: 139
Line coverage: 27.9% (2973 of 10630)
Covered lines: 2973
Uncovered lines: 7657
Coverable lines: 10630
Total lines: 31060
Branch coverage: 27.4% (1671 of 6097)
Covered branches: 1671
Total branches: 6097
Method coverage: Feature is only available for sponsors

Coverage

apidoc - 7%
Name Line Branch
apidoc 7% 8.4%
ApiDoctor.ConsoleApp.AppConfigFile 0% 0%
ApiDoctor.ConsoleApp.AppVeyor.BuildWorkerApi 0% 0%
ApiDoctor.ConsoleApp.Auth.BasicAccount 0%
ApiDoctor.ConsoleApp.Auth.OAuthAccount 0% 0%
ApiDoctor.ConsoleApp.Auth.OAuthAccountException 0%
ApiDoctor.ConsoleApp.Auth.OAuthTokenGenerator 0% 0%
ApiDoctor.ConsoleApp.Auth.TokenResponse 0%
ApiDoctor.ConsoleApp.BaseOptions 62.5% 100%
ApiDoctor.ConsoleApp.BasicCheckOptions 0%
ApiDoctor.ConsoleApp.CheckLinkOptions 0%
ApiDoctor.ConsoleApp.CheckMetadataOptions 0%
ApiDoctor.ConsoleApp.CheckResults 0% 0%
ApiDoctor.ConsoleApp.CheckServiceOptions 0% 0%
ApiDoctor.ConsoleApp.ConsoleAppLogger 0%
ApiDoctor.ConsoleApp.Constants 0%
ApiDoctor.ConsoleApp.DocSetOptions 71.4% 50%
ApiDoctor.ConsoleApp.FancyConsole 11.2% 5.8%
ApiDoctor.ConsoleApp.FixDocsOptions 75% 100%
ApiDoctor.ConsoleApp.GenerateDocsOptions 0%
ApiDoctor.ConsoleApp.GeneratePermissionFilesOptions 87.5% 83.3%
ApiDoctor.ConsoleApp.GenerateSnippetsOptions 0%
ApiDoctor.ConsoleApp.GitHelper 0% 0%
ApiDoctor.ConsoleApp.OutcomeExtensionMethods 0% 0%
ApiDoctor.ConsoleApp.PrintOptions 90% 90%
ApiDoctor.ConsoleApp.Program 3.9% 6.4%
ApiDoctor.ConsoleApp.PublishMetadataOptions 94.1% 100%
ApiDoctor.ConsoleApp.PublishOptions 70.5% 93.7%
ApiDoctor.ConsoleApp.TestReport 0% 0%
ApiDoctor.ConsoleApp.WildcardExtensions 0%
ApiDoctor.DocumentationGeneration - 72.9%
Name Line Branch
ApiDoctor.DocumentationGeneration 72.9% 52%
ApiDoctor.DocumentationGeneration.DocumentationGenerator 55.2% 14.2%
ApiDoctor.DocumentationGeneration.Extensions.DocumentationExtensions 73.3% 59%
ApiDoctor.DocumentationGeneration.Model.DocumentationComplexType 82.3% 50%
ApiDoctor.DocumentationGeneration.Model.DocumentationEntityType 100%
ApiDoctor.DocumentationGeneration.Model.DocumentationNavigationProperty 100%
ApiDoctor.DocumentationGeneration.Model.DocumentationProperty 100% 100%
ApiDoctor.DocumentationGeneration.Properties.Templates 54.5% 100%
ApiDoctor.Publishing - 0.7%
Name Line Branch
ApiDoctor.Publishing 0.7% 0%
ApiDoctor.Publishing.CSDL.CsdlExtensionMethods 0% 0%
ApiDoctor.Publishing.CSDL.CsdlWriter 0% 0%
ApiDoctor.Publishing.CSDL.CsdlWriterOptions 100%
ApiDoctor.Publishing.CSDL.MethodCollection 0% 0%
ApiDoctor.Publishing.CSDL.XmlSorter 0% 0%
ApiDoctor.Publishing.Html.ApiDocsConditionalTag 0% 0%
ApiDoctor.Publishing.Html.DocumentPublisherHtml 0% 0%
ApiDoctor.Publishing.Html.ExtendedElseTagDefinition 0%
ApiDoctor.Publishing.Html.FileTagDefinition 0% 0%
ApiDoctor.Publishing.Html.HtmlMustacheWriter 0% 0%
ApiDoctor.Publishing.Html.IfMatchTagDefinition 0%
ApiDoctor.Publishing.Html.TocItem 0% 0%
ApiDoctor.Publishing.Html.ValueObject`1 0%
ApiDoctor.Publishing.PathExtensionMethods 0% 0%
ApiDoctor.Publishing.Swagger.SwaggerAuth 0%
ApiDoctor.Publishing.Swagger.SwaggerExtensionMethods 0% 0%
ApiDoctor.Publishing.Swagger.SwaggerMethod 0%
ApiDoctor.Publishing.Swagger.SwaggerParameter 0%
ApiDoctor.Publishing.Swagger.SwaggerResponse 0%
ApiDoctor.Publishing.Swagger.SwaggerWriter 0% 0%
ApiDoctor.Validation - 43%
Name Line Branch
ApiDoctor.Validation 43% 40.1%
ApiDoctor.Validation.AccountTransforms 0%
ApiDoctor.Validation.ActionTransforms 0%
ApiDoctor.Validation.AuthenicationCredentials 0%
ApiDoctor.Validation.AuthScopeDefinition 0%
ApiDoctor.Validation.BackoffHelper 0% 0%
ApiDoctor.Validation.BasicCredentials 0%
ApiDoctor.Validation.CodeBlockAnnotation 39.8% 32.6%
ApiDoctor.Validation.Config.ApiRequirements 0%
ApiDoctor.Validation.Config.ApiRequirementsFile 100%
ApiDoctor.Validation.Config.ConditionalDocumentHeader 0% 0%
ApiDoctor.Validation.Config.ConfigFile 0%
ApiDoctor.Validation.Config.DocumentHeaderJsonConverter 0% 0%
ApiDoctor.Validation.Config.DocumentOutlineFile 100% 50%
ApiDoctor.Validation.Config.ExpectedDocumentHeader 0% 0%
ApiDoctor.Validation.Config.HttpRequestRequirements 0%
ApiDoctor.Validation.Config.HttpResponseRequirements 0%
ApiDoctor.Validation.Config.JsonSerializationRequirements 0%
ApiDoctor.Validation.Config.LinkValidationConfigFile 100%
ApiDoctor.Validation.Config.MetadataValidationConfigFile 100%
ApiDoctor.Validation.Config.MetadataValidationConfigs 50%
ApiDoctor.Validation.Config.ModelConfigs 66.6%
ApiDoctor.Validation.ConsoleLogHelper 0%
ApiDoctor.Validation.DocFile 46% 43.1%
ApiDoctor.Validation.DocSet 26% 8.5%
ApiDoctor.Validation.DocSetEventArgs 0%
ApiDoctor.Validation.DocumentHeader 13.7% 0%
ApiDoctor.Validation.EnumerationDefinition 0%
ApiDoctor.Validation.Error.IssueLogger 61.5% 48%
ApiDoctor.Validation.Error.UndocumentedPropertyWarning 100%
ApiDoctor.Validation.Error.ValidationError 69% 66.6%
ApiDoctor.Validation.Error.ValidationErrorExtensions 50% 25%
ApiDoctor.Validation.Error.ValidationMessage 100%
ApiDoctor.Validation.Error.ValidationResult`1 33.3% 0%
ApiDoctor.Validation.Error.ValidationWarning 100%
ApiDoctor.Validation.ErrorDefinition 0%
ApiDoctor.Validation.ExampleDefinition 0% 0%
ApiDoctor.Validation.ExtensionMethods 58.3% 58.1%
ApiDoctor.Validation.FirstPartyCredentials 0% 0%
ApiDoctor.Validation.FlexibleLowerCamelStringEnumConverter 0% 0%
ApiDoctor.Validation.Http.HttpParser 78.6% 72.9%
ApiDoctor.Validation.Http.HttpParserRequestException 0%
ApiDoctor.Validation.Http.HttpRequest 6% 0%
ApiDoctor.Validation.Http.HttpResponse 19.6% 26%
ApiDoctor.Validation.Http.HttpValidationExtensionMethods 15.3% 10.5%
ApiDoctor.Validation.HttpLog.HttpLogGenerator 0% 0%
ApiDoctor.Validation.HttpLog.PipeInfo 0%
ApiDoctor.Validation.HttpLog.SessionFlag 0%
ApiDoctor.Validation.HttpLog.SessionLogEntry 0%
ApiDoctor.Validation.HttpLog.SessionMetadata 0%
ApiDoctor.Validation.HttpLog.SessionTimers 0%
ApiDoctor.Validation.InternalScenarioExtensionMethods 0% 0%
ApiDoctor.Validation.ItemDefinition 100%
ApiDoctor.Validation.Json.JsonExample 100% 100%
ApiDoctor.Validation.Json.JsonPath 80.4% 83.4%
ApiDoctor.Validation.Json.JsonPathException 50%
ApiDoctor.Validation.Json.JsonResourceCollection 42.1% 42.3%
ApiDoctor.Validation.Json.JsonRewriter 80.7% 66.6%
ApiDoctor.Validation.Json.JsonSchema 61.7% 56.9%
ApiDoctor.Validation.Json.ValidationOptions 65.8% 50%
ApiDoctor.Validation.JsonResourceDefinition 50% 56.2%
ApiDoctor.Validation.Logging 0% 0%
ApiDoctor.Validation.MaxLengthAttribute 0% 0%
ApiDoctor.Validation.MetadataTransforms 0%
ApiDoctor.Validation.MethodDefinition 31.6% 18.6%
ApiDoctor.Validation.MethodValidationExtensionMethods 0% 0%
ApiDoctor.Validation.MultipartMime.MessagePart 92.3% 100%
ApiDoctor.Validation.MultipartMime.MimeContentType 60% 50%
ApiDoctor.Validation.MultipartMime.MultipartMimeContent 82.2% 92.8%
ApiDoctor.Validation.NoCredentials 0%
ApiDoctor.Validation.NtlmCredentials 0%
ApiDoctor.Validation.OAuthCredentials 0% 0%
ApiDoctor.Validation.OData.Action 66.6%
ApiDoctor.Validation.OData.ActionOrFunctionBase 55.8% 70.9%
ApiDoctor.Validation.OData.Annotation 18.1% 0%
ApiDoctor.Validation.OData.Annotations 83.3%
ApiDoctor.Validation.OData.ComplexType 66.6% 75%
ApiDoctor.Validation.OData.DataServices 100%
ApiDoctor.Validation.OData.EntityContainer 92.5% 100%
ApiDoctor.Validation.OData.EntityFramework 66.6% 0%
ApiDoctor.Validation.OData.EntitySet 66.6%
ApiDoctor.Validation.OData.EntityType 94.4% 87.5%
ApiDoctor.Validation.OData.EnumMember 66.6%
ApiDoctor.Validation.OData.EnumType 84.6%
ApiDoctor.Validation.OData.ExtensionMethods 81.6% 77.2%
ApiDoctor.Validation.OData.Function 75%
ApiDoctor.Validation.OData.Key 50%
ApiDoctor.Validation.OData.NavigationProperty 55.5%
ApiDoctor.Validation.OData.NavigationPropertyBinding 0%
ApiDoctor.Validation.OData.ODataCollection 94.7% 91.6%
ApiDoctor.Validation.OData.OdataNavigableExtensionMethods 46.1% 10.5%
ApiDoctor.Validation.OData.ODataParser 76% 85%
ApiDoctor.Validation.OData.ODataSimpleType 57.1%
ApiDoctor.Validation.OData.ODataTargetInfo 0%
ApiDoctor.Validation.OData.Parameter 87.5% 87.5%
ApiDoctor.Validation.OData.Property 81.8% 50%
ApiDoctor.Validation.OData.PropertyRef 50%
ApiDoctor.Validation.OData.PropertyValue 0% 0%
ApiDoctor.Validation.OData.Record 0%
ApiDoctor.Validation.OData.RecordCollection 0%
ApiDoctor.Validation.OData.ReturnType 80% 83.3%
ApiDoctor.Validation.OData.Schema 100%
ApiDoctor.Validation.OData.SchemaValidation 93.3% 90.6%
ApiDoctor.Validation.OData.Singleton 60%
ApiDoctor.Validation.OData.SortByAttribute 100%
ApiDoctor.Validation.OData.SortCollectionsHelper 26.5% 40%
ApiDoctor.Validation.OData.Term 0% 0%
ApiDoctor.Validation.OData.Transformation.AnnotationModification 0%
ApiDoctor.Validation.OData.Transformation.BaseModifications 100%
ApiDoctor.Validation.OData.Transformation.CommonModificationProperties 100%
ApiDoctor.Validation.OData.Transformation.ComplexTypeModification 0%
ApiDoctor.Validation.OData.Transformation.EntityContainerModification 0%
ApiDoctor.Validation.OData.Transformation.EntitySetModification 0%
ApiDoctor.Validation.OData.Transformation.EntityTypeModification 0%
ApiDoctor.Validation.OData.Transformation.EnumerationTypeModification 0%
ApiDoctor.Validation.OData.Transformation.FunctionModification 0%
ApiDoctor.Validation.OData.Transformation.KeyModification 0%
ApiDoctor.Validation.OData.Transformation.MemberModification 0%
ApiDoctor.Validation.OData.Transformation.NavigationPropertyBindingModifica
tion
0%
ApiDoctor.Validation.OData.Transformation.ParameterModification 0%
ApiDoctor.Validation.OData.Transformation.PropertyModification 100%
ApiDoctor.Validation.OData.Transformation.PropertyRefModification 0%
ApiDoctor.Validation.OData.Transformation.PublishSchemaChanges 0%
ApiDoctor.Validation.OData.Transformation.PublishSchemaChangesConfigFile 0%
ApiDoctor.Validation.OData.Transformation.ReturnTypeModification 0%
ApiDoctor.Validation.OData.Transformation.SchemaConfig 88.2%
ApiDoctor.Validation.OData.Transformation.SchemaConfigFile 66.6%
ApiDoctor.Validation.OData.Transformation.SchemaDiffConfig 0%
ApiDoctor.Validation.OData.Transformation.SchemaModifications 0%
ApiDoctor.Validation.OData.Transformation.TermModification 0%
ApiDoctor.Validation.OData.Transformation.TransformationHelper 58.8% 69.2%
ApiDoctor.Validation.OData.Transformation.XmlBackedTransformableObject 33.3%
ApiDoctor.Validation.OData.XmlBackedObject 50% 0%
ApiDoctor.Validation.OData.XmlParseHelper 100% 100%
ApiDoctor.Validation.ODataAnnotations 0%
ApiDoctor.Validation.ODataCapabilities 0% 0%
ApiDoctor.Validation.PageAnnotation 21.7% 0%
ApiDoctor.Validation.ParameterDataType 68.5% 48.7%
ApiDoctor.Validation.ParameterDefinition 35.4% 18.5%
ApiDoctor.Validation.Params.BasicRequestDefinition 0% 0%
ApiDoctor.Validation.Params.CannedRequestDefinition 0%
ApiDoctor.Validation.Params.CSharpEval 0% 0%
ApiDoctor.Validation.Params.PlaceholderValue 0% 0%
ApiDoctor.Validation.Params.PlaceholderValueNotFoundException 0%
ApiDoctor.Validation.Params.RequestDefinitionExtensions 0% 0%
ApiDoctor.Validation.Params.ScenarioDefinition 0%
ApiDoctor.Validation.Params.TestSetupRequestDefinition 0% 0%
ApiDoctor.Validation.Properties.Resources 50% 100%
ApiDoctor.Validation.ResourceDefinition 66.6% 7.1%
ApiDoctor.Validation.ScenarioExtensionMethods 0% 0%
ApiDoctor.Validation.ScenarioFile 100% 50%
ApiDoctor.Validation.SchemaBuildException 100%
ApiDoctor.Validation.SingleOrArrayConverter`1 27.2% 25%
ApiDoctor.Validation.StringSuggestions 100% 94.1%
ApiDoctor.Validation.SupplementalFile 0% 0%
ApiDoctor.Validation.TableSpec.TableDecoder 41.6%
ApiDoctor.Validation.TableSpec.TableDefinition 100%
ApiDoctor.Validation.TableSpec.TableParserConfig 100%
ApiDoctor.Validation.TableSpec.TableParserConfigFile 100% 50%
ApiDoctor.Validation.TableSpec.TableRule 100%
ApiDoctor.Validation.TableSpec.TableSpecConverter 46.1% 48.4%
ApiDoctor.Validation.Tags.TagProcessor 0% 0%
ApiDoctor.Validation.Utility.ExtensionMethods 100% 100%
ApiDoctor.Validation.Utility.MergableAttribute 100%
ApiDoctor.Validation.Utility.MergePolicyAttribute 100%
ApiDoctor.Validation.Utility.ObjectGraphMerger`1 77.4% 64.7%
ApiDoctor.Validation.Utility.ObjectGraphMergerException 0%
ApiDoctor.Validation.ValidationConfig 87.5%
ApiDoctor.Validation.ValidationResults 0% 0%
ApiDoctor.Validation.Writers.DefaultPublishOptions 0%
ApiDoctor.Validation.Writers.DocumentPublisher 0% 0%
ApiDoctor.Validation.Writers.MarkdownPublisher 0% 0%
ApiDoctor.Validation.Writers.OutlinePublisher 0% 0%
ApiDoctor.Validation.Writers.ScanRuleAttribute 0%
ApiDoctor.Validation.Writers.ValidationMessageEventArgs 0%

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ODataParser.BuildJsonExample example-generation state leaking across calls by removing static caches and introducing per-invocation recursion tracking, and it adds a broad set of NUnit unit tests covering OData parsing, navigation, transformations, and validation behaviors.

Changes:

  • Replace static example-generation caches in ODataParser with per-call visited/cache state passed through recursion.
  • Add comprehensive unit tests for OData parsing/serialization, navigation, schema validation, transformations, extension methods, enums, and actions/functions.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ApiDoctor.Validation/OData/ODataParser.cs Removes static state from JSON example generation; threads recursion/caching state through helper methods.
ApiDoctor.Validation.UnitTests/TransformationHelperTests.cs Adds tests for transformation application and collection modification behaviors.
ApiDoctor.Validation.UnitTests/StringSuggestionsAndXmlParseHelperTests.cs Adds tests for string suggestion scoring and XML parse helper extension methods.
ApiDoctor.Validation.UnitTests/SchemaValidationTests.cs Adds tests validating schema type resolution behavior and console reporting for unknown types.
ApiDoctor.Validation.UnitTests/ODataParserTests.cs Adds tests for EDMX parsing, inheritance merging, JSON example generation, and round-trip serialization.
ApiDoctor.Validation.UnitTests/ODataNavigationTests.cs Adds tests for URI navigation across entity/container/collection/complex type scenarios.
ApiDoctor.Validation.UnitTests/ODataExtensionMethodTests.cs Adds tests for type string extension helpers and EntityFramework type lookup helpers.
ApiDoctor.Validation.UnitTests/EnumTypeTests.cs Adds tests for enum parsing and navigability boundaries.
ApiDoctor.Validation.UnitTests/ActionOrFunctionTests.cs Adds tests for substitution rules and function/action deserialization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

rgregg-msft and others added 5 commits March 17, 2026 14:33
Without this, coverlet instruments the test project itself, which always
shows 100% (the runner executes every test method) and skews the report.
The Exclude filter restricts coverage to production assemblies only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Using Include is more precise than Exclude: only first-party assemblies
(ApiDoctor.*, apidoc, ObjectGraphMergeUtility) are measured. This drops
MarkdownDeep (third-party OSS), all NuGet dependencies, and test projects
from the report without having to enumerate them explicitly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ODataParser: thread metadataValidationConfigs through ExampleOfType and
  ObjectExampleForType so nested @odata.type values respect ValidateNamespace
  and AliasNamespace settings consistently at all nesting levels
- ODataParserTests: update two comments that still referenced the removed
  static fields (visitedProperties, generatedExamples); reword to describe
  the current per-call invariant being tested
- StringSuggestionsAndXmlParseHelperTests: add explicit `using ApiDoctor.Validation`
  for StringSuggestions; rename test to SuggestStringFromCollection_EmptyCollection_ReturnsMaxScore
  to match what is actually asserted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[ApiDoctor.*] in the Include pattern also matches ApiDoctor.Validation.UnitTests.
Add an Exclude for *.UnitTests and *.Tests so test assemblies are filtered out
even when they satisfy the Include glob.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the primary user-facing CLI option classes:

- CommandLineOptionsVerbTests (12): assert all verb constants match their
  expected CLI string values — catches accidental renames that would silently
  break the CLI
- BaseOptionsTests (5): PageParameterDict URL-encoded query string parsing
  (null, empty, single, multiple params, case-insensitive keys); base
  HasRequiredProperties always returns true
- PrintOptionsTests (6): HasRequiredProperties validation — returns false when
  no print flags are set, true for each individual flag and for combinations
- FixDocsOptionsTests (8): Matches property parsing — null/empty/whitespace →
  empty set; comma and semicolon separators; whitespace trimming;
  case-insensitive comparison
- PublishOptionsTests (9): FilesToPublish splitting (null, single, multi,
  round-trip setter); HasRequiredProperties for Markdown (always OK), Mustache
  (requires template), Swagger2 (requires title/description/version, reports
  all missing fields)
- GeneratePermissionFilesOptionsTests (4): HasRequiredProperties — bootstrapping
  mode skips file requirement; non-bootstrapping requires non-whitespace file
- PublishMetadataOptionsTests (4): GetOptions() mapping — all fields copied
  correctly; Namespaces split on comma and semicolon; null Namespaces → null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants