-
Notifications
You must be signed in to change notification settings - Fork 12
[DMS-912] Enable smoke test suites against local DMS stack #960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,69 +77,177 @@ | |
|
|
||
| Import-Module -Name "$PSScriptRoot/eng/build-helpers.psm1" -Force | ||
|
|
||
| $openApiGeneratorVersion = "7.19.0" | ||
| $openApiGeneratorJar = "openApi-codegen-cli-$openApiGeneratorVersion.jar" | ||
|
|
||
| $solutionRoot = "$PSScriptRoot/$OutputFolder" | ||
| $projectPath = "$solutionRoot/src/$PackageName/$PackageName.csproj" | ||
| $nuspecPath = "$PSScriptRoot/eng/sdkGen/$PackageName.nuspec" | ||
|
|
||
| function DownloadCodeGen { | ||
| if (-not (Test-Path -Path openApi-codegen-cli.jar)) { | ||
| Invoke-WebRequest -OutFile openApi-codegen-cli.jar https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.9.0/openapi-generator-cli-7.9.0.jar | ||
| if (-not (Test-Path -Path $openApiGeneratorJar)) { | ||
| Invoke-WebRequest -OutFile $openApiGeneratorJar https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$openApiGeneratorVersion/openapi-generator-cli-$openApiGeneratorVersion.jar | ||
| } | ||
| } | ||
|
|
||
| function GenerateSdk { | ||
| param ( | ||
| [string] | ||
| $ApiPackage, | ||
| function Rename-DescriptorOperationId { | ||
| param([string]$old) | ||
| if ($old -match 'ById$') { | ||
| return ($old -replace 'ById$', 'DescriptorById') | ||
| } elseif ($old -match 's$') { | ||
| return ($old -replace 's$', 'Descriptors') | ||
| } else { | ||
| return "${old}Descriptor" | ||
| } | ||
| } | ||
|
|
||
| function Merge-DmsSpecs { | ||
Check warningCode scanning / PSScriptAnalyzer Cmdlet Singular Noun Warning
The cmdlet 'Merge-DmsSpecs' uses a plural noun. A singular noun should be used instead.
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Permanent improvement. The two-pass flow (one |
||
| # Merges resources-spec and descriptors-spec into a single OpenAPI document so the generator | ||
| # runs once and emits a complete HostConfiguration.cs / IApi.cs. The two-pass flow used to | ||
| # let the descriptors pass clobber the resources-pass HostConfiguration, leaving resource | ||
| # APIs unregistered in DI and the smoke test tool throwing NREs on every resource call. | ||
| param( | ||
| [string]$ResourcesEndpoint, | ||
| [string]$DescriptorsEndpoint | ||
| ) | ||
|
|
||
| $resources = (Invoke-WebRequest -Uri $ResourcesEndpoint).Content | ConvertFrom-Json -Depth 100 -AsHashtable | ||
| $descriptors = (Invoke-WebRequest -Uri $DescriptorsEndpoint).Content | ConvertFrom-Json -Depth 100 -AsHashtable | ||
|
|
||
| # Rename descriptor operationIds that collide with resource ones (e.g. getGradingPeriods | ||
| # appears in both specs; without renaming we'd get duplicate C# method names in Apis.All). | ||
| $resourceIds = @{} | ||
| foreach ($pathOps in $resources.paths.Values) { | ||
| foreach ($op in $pathOps.Values) { | ||
| if ($op -is [System.Collections.IDictionary] -and $op.ContainsKey('operationId')) { | ||
| $resourceIds[$op.operationId] = $true | ||
| } | ||
| } | ||
| } | ||
| foreach ($pathOps in $descriptors.paths.Values) { | ||
| foreach ($op in $pathOps.Values) { | ||
| if ($op -is [System.Collections.IDictionary] -and $op.ContainsKey('operationId') -and $resourceIds.ContainsKey($op.operationId)) { | ||
| $op.operationId = Rename-DescriptorOperationId $op.operationId | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Union descriptor paths/schemas/tags into resources. Parameters/responses/securitySchemes | ||
| # are identical across both specs (verified by inspection) so the resources copy is kept. | ||
| foreach ($pathName in $descriptors.paths.Keys) { | ||
| if (-not $resources.paths.ContainsKey($pathName)) { | ||
| $resources.paths[$pathName] = $descriptors.paths[$pathName] | ||
| } | ||
| } | ||
| foreach ($schemaName in $descriptors.components.schemas.Keys) { | ||
| if (-not $resources.components.schemas.ContainsKey($schemaName)) { | ||
| $resources.components.schemas[$schemaName] = $descriptors.components.schemas[$schemaName] | ||
| } | ||
| } | ||
| $existingTagNames = @{} | ||
| foreach ($t in $resources.tags) { $existingTagNames[$t.name] = $true } | ||
| foreach ($t in $descriptors.tags) { | ||
| if (-not $existingTagNames.ContainsKey($t.name)) { | ||
| $resources.tags += $t | ||
| } | ||
| } | ||
|
|
||
| [string] | ||
| $ModelPackage, | ||
| # Drop required arrays on Homograph* schemas so the generichost-library generator omits its | ||
| # throw-on-missing-required validation. The smoke test tool's data factory cannot populate | ||
| # required props on these extension models, and the server-side spec still enforces them. | ||
| foreach ($schemaName in @($resources.components.schemas.Keys)) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Workaround. OpenAPI Generator 7.19.0's Removable when either (a) the |
||
| if ($schemaName.StartsWith('Homograph')) { | ||
| $schema = $resources.components.schemas[$schemaName] | ||
| if ($schema -is [System.Collections.IDictionary] -and $schema.ContainsKey('required')) { | ||
| $schema.Remove('required') | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return $resources | ||
| } | ||
|
|
||
| [string] | ||
| $Endpoint | ||
| function GenerateSdk { | ||
| param ( | ||
| [string]$ApiPackage, | ||
| [string]$ModelPackage, | ||
| [System.Collections.IDictionary]$Spec | ||
| ) | ||
|
|
||
| # Download and parse OpenAPI spec | ||
| $spec = Invoke-WebRequest -Uri $Endpoint | ConvertFrom-Json | ||
| # Rewrite tags on non-ed-fi paths whose tag also appears on /ed-fi/* paths. Without this, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Workaround. MetaEd.js emits Removable when |
||
| # /ed-fi/contacts and /homograph/contacts share tag 'contacts' and land on the same | ||
| # ContactsApi, which the smoke test tool's "one Post per Api class" categorizer can't | ||
| # disambiguate. The generator emits a distinct *Api class per tag, so prefixing the tag | ||
| # with the namespace splits the colliding endpoints into separate classes. | ||
| $coreTags = @{} | ||
| foreach ($pathName in @($Spec.paths.Keys)) { | ||
| if ($pathName.StartsWith('/ed-fi/')) { | ||
| foreach ($verb in @($Spec.paths[$pathName].Keys)) { | ||
| $op = $Spec.paths[$pathName][$verb] | ||
| if ($op -is [System.Collections.IDictionary] -and $op.ContainsKey('tags') -and $op['tags']) { | ||
| foreach ($t in $op['tags']) { $coreTags[$t] = $true } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| foreach ($pathName in @($Spec.paths.Keys)) { | ||
| if ($pathName -match '^/(?<ns>[^/]+)/' -and $matches.ns -ne 'ed-fi') { | ||
| $nsSafe = ($matches.ns -replace '[^A-Za-z0-9]', '') | ||
| foreach ($verb in @($Spec.paths[$pathName].Keys)) { | ||
| $op = $Spec.paths[$pathName][$verb] | ||
| if ($op -is [System.Collections.IDictionary] -and $op.ContainsKey('tags') -and $op['tags']) { | ||
| $op['tags'] = @($op['tags'] | ForEach-Object { if ($coreTags.ContainsKey($_)) { "${nsSafe}_$_" } else { $_ } }) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Find all operationIds that contain an underscore | ||
| $operationIds = $spec.paths.PSObject.Properties.Value | ForEach-Object { | ||
| $_.PSObject.Properties.Value | Where-Object { $_.operationId -and $_.operationId -like "*_*" } | ForEach-Object { $_.operationId } | ||
| # Build --operation-id-name-mappings for underscore-bearing operationIds so the generator | ||
| # preserves the namespace separator in C# method names (post_HomographContact stays | ||
| # Post_HomographContact rather than collapsing to PostHomographContact). | ||
| $operationIds = @() | ||
| foreach ($pathOps in $Spec.paths.Values) { | ||
| foreach ($op in $pathOps.Values) { | ||
| if ($op -is [System.Collections.IDictionary] -and $op.ContainsKey('operationId') -and $op.operationId -like "*_*") { | ||
| $operationIds += $op.operationId | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Normalize operationId to camelCase without underscores (for the left side of mapping) | ||
| function Normalize-OperationId { | ||
| param($opId) | ||
| $parts = $opId -split '_' | ||
| $camel = $parts[0] + ($parts[1..($parts.Count-1)] | ForEach-Object { $_.Substring(0,1).ToUpper() + $_.Substring(1) } | ForEach-Object { $_ }) -join '' | ||
| $camel = $parts[0] + (($parts[1..($parts.Count-1)] | ForEach-Object { $_.Substring(0,1).ToUpper() + $_.Substring(1) }) -join '') | ||
| return $camel | ||
| } | ||
|
|
||
| # Capitalize the first character of the string | ||
| function Capitalize-FirstChar { | ||
| param($s) | ||
| if ($s.Length -eq 0) { return $s } | ||
| return $s.Substring(0,1).ToUpper() + $s.Substring(1) | ||
| } | ||
|
|
||
| # Build mappings string: left = normalized, right = original with first char uppercased | ||
| $mappings = ($operationIds | Sort-Object -Unique | ForEach-Object { "$(Normalize-OperationId $_)=$(Capitalize-FirstChar $_)" }) -join "," | ||
| # Example --operation-id-name-mappings deleteHomographContactsById=Delete_HomographContactsById | ||
|
|
||
| & java -Xmx5g -jar openApi-codegen-cli.jar generate ` | ||
| -g csharp ` | ||
| -i $Endpoint ` | ||
| --api-package $ApiPackage ` | ||
| --model-package $ModelPackage ` | ||
| -o $OutputFolder ` | ||
| --operation-id-name-mappings $mappings ` | ||
| --additional-properties "packageName=$PackageName,targetFramework=net8.0,netCoreProjectFile=true" ` | ||
| --global-property modelTests=false ` | ||
| --global-property apiTests=false ` | ||
| --global-property apiDocs=false ` | ||
| --global-property modelDocs=false ` | ||
| --skip-validate-spec | ||
|
|
||
| $specTempPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "openapi-spec-$([System.Guid]::NewGuid()).json") | ||
| $Spec | ConvertTo-Json -Depth 100 -Compress | Set-Content -Path $specTempPath -Encoding UTF8NoBOM | ||
|
|
||
| try { | ||
| & java -Xmx5g -jar $openApiGeneratorJar generate ` | ||
| -g csharp ` | ||
| -i $specTempPath ` | ||
| --api-package $ApiPackage ` | ||
| --model-package $ModelPackage ` | ||
| -o $OutputFolder ` | ||
| --operation-id-name-mappings $mappings ` | ||
| --additional-properties "packageName=$PackageName,targetFramework=net10.0,netCoreProjectFile=true" ` | ||
| --global-property modelTests=false ` | ||
| --global-property apiTests=false ` | ||
| --global-property apiDocs=false ` | ||
| --global-property modelDocs=false ` | ||
| --skip-validate-spec | ||
| } | ||
| finally { | ||
| Remove-Item $specTempPath -ErrorAction SilentlyContinue | ||
| } | ||
| } | ||
|
|
||
| function BuildSdk { | ||
|
|
@@ -198,16 +306,18 @@ | |
| function Invoke-BuildAndGenerateSdk { | ||
| Invoke-Step { DownloadCodeGen } | ||
|
|
||
| if ($PackageName -eq "EdFi.DmsApi.TestSdk") { | ||
| Invoke-Step { GenerateSdk -ApiPackage "Apis.All" -ModelPackage "Models.All" -Endpoint "$DmsUrl/metadata/specifications/resources-spec.json" } | ||
| Invoke-Step { GenerateSdk -ApiPackage "Apis.All" -ModelPackage "Models.All" -Endpoint "$DmsUrl/metadata/specifications/descriptors-spec.json" } | ||
| } elseif ($PackageName -eq "EdFi.DmsApi.Sdk") { | ||
| Invoke-Step { GenerateSdk -ApiPackage "Apis.Ed_Fi" -ModelPackage "Models.Ed_Fi" -Endpoint "$DmsUrl/metadata/specifications/resources-spec.json" } | ||
| Invoke-Step { GenerateSdk -ApiPackage "Apis.Ed_Fi" -ModelPackage "Models.Ed_Fi" -Endpoint "$DmsUrl/metadata/specifications/descriptors-spec.json" } | ||
| } else { | ||
| throw "Unknown PackageName value: $PackageName" | ||
| $mergedSpec = Merge-DmsSpecs ` | ||
| -ResourcesEndpoint "$DmsUrl/metadata/specifications/resources-spec.json" ` | ||
| -DescriptorsEndpoint "$DmsUrl/metadata/specifications/descriptors-spec.json" | ||
|
|
||
| $packagePair = switch ($PackageName) { | ||
| "EdFi.DmsApi.TestSdk" { @{ Api = "Apis.All"; Model = "Models.All" } } | ||
| "EdFi.DmsApi.Sdk" { @{ Api = "Apis.Ed_Fi"; Model = "Models.Ed_Fi" } } | ||
| default { throw "Unknown PackageName value: $PackageName" } | ||
| } | ||
|
|
||
| Invoke-Step { GenerateSdk -ApiPackage $packagePair.Api -ModelPackage $packagePair.Model -Spec $mergedSpec } | ||
|
|
||
| Invoke-Step { BuildSdk } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workaround. MetaEd.js emits operationIds like
getGradingPeriodson both/ed-fi/gradingPeriods(resources-spec) and/ed-fi/gradingPeriodDescriptors(descriptors-spec). Once merged into a single spec, duplicate operationIds produce duplicate C# method names inApis.All.Removable when
reference/design/backend-redesign/epics/08-relational-read-path/07-disambiguate-descriptor-operationids.mdlands. Once descriptor operationIds carry theDescriptor/Descriptorssuffix in MetaEd.js output, this helper plus the descriptor loop inMerge-DmsSpecs(lines 127-133) become dead code.