diff --git a/cmd/aws-model-coverage/main.go b/cmd/aws-model-coverage/main.go index 8129de6e1..8cd344f2e 100644 --- a/cmd/aws-model-coverage/main.go +++ b/cmd/aws-model-coverage/main.go @@ -84,7 +84,10 @@ func writePages(outputDir string, coverages []awsmodel.OperationCoverage) error } for _, coverage := range coverages { - page := pages.Services[coverage.Service] + page, ok := pages.Services[coverage.Service] + if !ok { + continue + } intro, err := readIntro(outputDir, page.Slug) if err != nil { return err diff --git a/docs/compatibility/acm-api-coverage/README.md b/docs/compatibility/acm-api-coverage/README.md index 2774dfd6c..2355d0c08 100644 --- a/docs/compatibility/acm-api-coverage/README.md +++ b/docs/compatibility/acm-api-coverage/README.md @@ -30,14 +30,6 @@ Terraform's canonical certificate, DNS record and validation flow works unmodifi ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AddTagsToCertificate` | ✅ Implemented | diff --git a/docs/compatibility/aws-api-coverage/README.md b/docs/compatibility/aws-api-coverage/README.md index 3e311fb80..f641ebf9b 100644 --- a/docs/compatibility/aws-api-coverage/README.md +++ b/docs/compatibility/aws-api-coverage/README.md @@ -29,17 +29,4 @@ Spinifex serves the AWS APIs below. Every page counts the operations in the pinn | [ELBv2](/docs/elbv2-api-coverage) | 2015-12-01 | 33 | 46 | 71.7% | | [IAM](/docs/iam-api-coverage) | 2010-05-08 | 75 | 159 | 47.2% | | [RDS](/docs/rds-api-coverage) | 2014-10-31 | 26 | 162 | 16.0% | -| [S3](/docs/s3-api-coverage) | 2006-03-01 | — | 99 | — | | [STS](/docs/sts-api-coverage) | 2011-06-15 | 4 | 8 | 50.0% | - -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - -### What "implemented" means here - -An operation is implemented when the gateway binds it to a real handler. These pages are generated from those dispatch tables on every build, so they cannot go stale against the code. That is a mechanical fact, and it is the only claim they make. It does not say the handler honours every parameter AWS models, and no page reports parameter or field-level detail: a handler receives a fully-populated typed SDK struct, so nothing distinguishes a field it honours from one it ignores. Behavioural conformance is measured separately by the integration conformance suite. diff --git a/docs/compatibility/aws-api-coverage/intro.md b/docs/compatibility/aws-api-coverage/intro.md deleted file mode 100644 index b29835c37..000000000 --- a/docs/compatibility/aws-api-coverage/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -### What "implemented" means here - -An operation is implemented when the gateway binds it to a real handler. These pages are generated from those dispatch tables on every build, so they cannot go stale against the code. That is a mechanical fact, and it is the only claim they make. It does not say the handler honours every parameter AWS models, and no page reports parameter or field-level detail: a handler receives a fully-populated typed SDK struct, so nothing distinguishes a field it honours from one it ignores. Behavioural conformance is measured separately by the integration conformance suite. diff --git a/docs/compatibility/ec2-api-coverage/README.md b/docs/compatibility/ec2-api-coverage/README.md index 72f18031a..e84111b69 100644 --- a/docs/compatibility/ec2-api-coverage/README.md +++ b/docs/compatibility/ec2-api-coverage/README.md @@ -28,14 +28,6 @@ Spot Instance Requests are a mock over the on-demand `RunInstances` path. A requ ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AcceptAddressTransfer` | ❌ Not implemented | diff --git a/docs/compatibility/ecr-api-coverage/README.md b/docs/compatibility/ecr-api-coverage/README.md index 49a4356e9..b02d531eb 100644 --- a/docs/compatibility/ecr-api-coverage/README.md +++ b/docs/compatibility/ecr-api-coverage/README.md @@ -30,14 +30,6 @@ Vulnerability scanning is not offered, so the image-scanning operations refuse d ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `BatchCheckLayerAvailability` | 🟡 Stub | diff --git a/docs/compatibility/ecs-api-coverage/README.md b/docs/compatibility/ecs-api-coverage/README.md index 178d298eb..ee96f7144 100644 --- a/docs/compatibility/ecs-api-coverage/README.md +++ b/docs/compatibility/ecs-api-coverage/README.md @@ -26,14 +26,6 @@ Clusters, services and tasks run on EC2 container instances. There is no Fargate ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `CreateCapacityProvider` | ✅ Implemented | diff --git a/docs/compatibility/eks-api-coverage/README.md b/docs/compatibility/eks-api-coverage/README.md index f47a6f36c..87eb1f932 100644 --- a/docs/compatibility/eks-api-coverage/README.md +++ b/docs/compatibility/eks-api-coverage/README.md @@ -37,14 +37,6 @@ None is callable with an ordinary tenant credential. Each names the target accou ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AssociateAccessPolicy` | ✅ Implemented | diff --git a/docs/compatibility/elbv2-api-coverage/README.md b/docs/compatibility/elbv2-api-coverage/README.md index 6da72a1f4..bb8ddf7ff 100644 --- a/docs/compatibility/elbv2-api-coverage/README.md +++ b/docs/compatibility/elbv2-api-coverage/README.md @@ -26,14 +26,6 @@ The data plane is a system-managed load balancer VM, launched automatically duri ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AddListenerCertificates` | ✅ Implemented | diff --git a/docs/compatibility/iam-api-coverage/README.md b/docs/compatibility/iam-api-coverage/README.md index 7d3af9890..10ff42a6f 100644 --- a/docs/compatibility/iam-api-coverage/README.md +++ b/docs/compatibility/iam-api-coverage/README.md @@ -26,14 +26,6 @@ All IAM operations are account-scoped. The root user of an account bypasses poli ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AddClientIDToOpenIDConnectProvider` | ❌ Not implemented | diff --git a/docs/compatibility/pages.json b/docs/compatibility/pages.json index 3f9e04941..a16f1a2a4 100644 --- a/docs/compatibility/pages.json +++ b/docs/compatibility/pages.json @@ -6,7 +6,13 @@ "title": "AWS API Coverage", "seoTitle": "Which AWS API Operations Spinifex Implements — Spinifex Docs", "description": "Operation-level coverage of every AWS API Spinifex serves, generated from its gateway dispatch tables and the pinned AWS SDK service models on each build.", - "tags": ["aws", "compatibility", "coverage", "api", "operations"] + "tags": [ + "aws", + "compatibility", + "coverage", + "api", + "operations" + ] }, "services": { "acm": { @@ -15,7 +21,14 @@ "title": "ACM API Coverage", "seoTitle": "AWS Certificate Manager API Coverage — Spinifex Docs", "description": "Every operation in the AWS Certificate Manager API model and whether Spinifex implements it, generated from the gateway dispatch tables on each build.", - "tags": ["aws", "compatibility", "coverage", "acm", "certificates", "tls"] + "tags": [ + "aws", + "compatibility", + "coverage", + "acm", + "certificates", + "tls" + ] }, "ec2": { "slug": "ec2-api-coverage", @@ -23,7 +36,14 @@ "title": "EC2 API Coverage", "seoTitle": "Amazon EC2 API Coverage on Spinifex — Spinifex Docs", "description": "Every operation in the Amazon EC2 API model and whether Spinifex implements it, covering instances, EBS volumes, VPC networking, tags and security groups.", - "tags": ["aws", "compatibility", "coverage", "ec2", "compute", "vpc"] + "tags": [ + "aws", + "compatibility", + "coverage", + "ec2", + "compute", + "vpc" + ] }, "ecr": { "slug": "ecr-api-coverage", @@ -31,7 +51,14 @@ "title": "ECR API Coverage", "seoTitle": "Elastic Container Registry API Coverage — Spinifex Docs", "description": "Every operation in the Amazon ECR API model and whether Spinifex implements it, alongside the OCI distribution endpoint that carries the image layers.", - "tags": ["aws", "compatibility", "coverage", "ecr", "containers", "registry"] + "tags": [ + "aws", + "compatibility", + "coverage", + "ecr", + "containers", + "registry" + ] }, "ecs": { "slug": "ecs-api-coverage", @@ -39,7 +66,14 @@ "title": "ECS API Coverage", "seoTitle": "Elastic Container Service API Coverage — Spinifex Docs", "description": "Every operation in the Amazon ECS API model and whether Spinifex implements it, covering clusters, services, tasks and container instances on each build.", - "tags": ["aws", "compatibility", "coverage", "ecs", "containers", "orchestration"] + "tags": [ + "aws", + "compatibility", + "coverage", + "ecs", + "containers", + "orchestration" + ] }, "eks": { "slug": "eks-api-coverage", @@ -47,7 +81,14 @@ "title": "EKS API Coverage", "seoTitle": "Elastic Kubernetes Service API Coverage — Spinifex Docs", "description": "Every operation in the Amazon EKS API model and whether Spinifex implements it, covering clusters, nodegroups, add-ons and access entries on each build.", - "tags": ["aws", "compatibility", "coverage", "eks", "kubernetes", "containers"] + "tags": [ + "aws", + "compatibility", + "coverage", + "eks", + "kubernetes", + "containers" + ] }, "elasticloadbalancingv2": { "slug": "elbv2-api-coverage", @@ -55,7 +96,14 @@ "title": "ELBv2 API Coverage", "seoTitle": "Elastic Load Balancing v2 API Coverage — Spinifex Docs", "description": "Every operation in the Elastic Load Balancing v2 API model and whether Spinifex implements it, for both the Application and Network Load Balancers it serves.", - "tags": ["aws", "compatibility", "coverage", "elbv2", "load balancing", "networking"] + "tags": [ + "aws", + "compatibility", + "coverage", + "elbv2", + "load balancing", + "networking" + ] }, "iam": { "slug": "iam-api-coverage", @@ -63,7 +111,14 @@ "title": "IAM API Coverage", "seoTitle": "AWS IAM API Operation Coverage on Spinifex — Spinifex Docs", "description": "Every operation in the AWS IAM API model and whether Spinifex implements it, covering users, roles, policies, groups and instance profiles on each build.", - "tags": ["aws", "compatibility", "coverage", "iam", "identity", "policies"] + "tags": [ + "aws", + "compatibility", + "coverage", + "iam", + "identity", + "policies" + ] }, "rds": { "slug": "rds-api-coverage", @@ -71,15 +126,15 @@ "title": "RDS API Coverage", "seoTitle": "Relational Database Service API Coverage — Spinifex Docs", "description": "Every operation in the Amazon RDS API model and whether Spinifex implements it, for the managed PostgreSQL and MariaDB engines it offers on each build.", - "tags": ["aws", "compatibility", "coverage", "rds", "databases", "postgresql", "mariadb"] - }, - "s3": { - "slug": "s3-api-coverage", - "name": "S3", - "title": "S3 API Coverage", - "seoTitle": "Amazon S3 API Compatibility on Spinifex — Spinifex Docs", - "description": "How Spinifex serves the Amazon S3 API through Predastore, and why its coverage is measured there rather than derived from a gateway dispatch table here.", - "tags": ["aws", "compatibility", "coverage", "s3", "storage", "predastore"] + "tags": [ + "aws", + "compatibility", + "coverage", + "rds", + "databases", + "postgresql", + "mariadb" + ] }, "sts": { "slug": "sts-api-coverage", @@ -87,7 +142,14 @@ "title": "STS API Coverage", "seoTitle": "AWS Security Token Service API Coverage — Spinifex Docs", "description": "Every operation in the AWS STS API model and whether Spinifex implements it, covering role assumption, session tokens and web identity federation (IRSA).", - "tags": ["aws", "compatibility", "coverage", "sts", "identity", "credentials"] + "tags": [ + "aws", + "compatibility", + "coverage", + "sts", + "identity", + "credentials" + ] } } } diff --git a/docs/compatibility/rds-api-coverage/README.md b/docs/compatibility/rds-api-coverage/README.md index 255bb6f48..b8294d34c 100644 --- a/docs/compatibility/rds-api-coverage/README.md +++ b/docs/compatibility/rds-api-coverage/README.md @@ -45,14 +45,6 @@ A parameter whose omission would create a false safety, security or availability ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AddRoleToDBCluster` | ❌ Not implemented | diff --git a/docs/compatibility/s3-api-coverage/README.md b/docs/compatibility/s3-api-coverage/README.md deleted file mode 100644 index 974e9ac11..000000000 --- a/docs/compatibility/s3-api-coverage/README.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: "S3 API Coverage" -seoTitle: "Amazon S3 API Compatibility on Spinifex — Spinifex Docs" -description: "How Spinifex serves the Amazon S3 API through Predastore, and why its coverage is measured there rather than derived from a gateway dispatch table here." -category: "Coverage" -sections: - - overview -tags: - - aws - - compatibility - - coverage - - s3 - - storage - - predastore ---- - -# S3 API Coverage - -## Overview - -The S3 API model pins **99** operations at API version `2006-03-01`, but Spinifex's coverage of them is not mechanically enumerable: Spinifex delegates the S3 REST surface to Predastore, which has no operation-name dispatch table to compare mechanically. - -### Where S3 is served - -Spinifex does not dispatch S3 operations. The REST surface is delegated whole to **Predastore**, which serves it directly, so there is no operation-name dispatch table in the gateway to compare against the model — the comparison that produces every other page on this site has nothing to read here. - -That is why this page reports no percentage. Claiming one from an empty dispatch table would read as 0% implemented, which is the opposite of the truth: S3 is one of the most complete surfaces in the platform. - -Predastore publishes its own measured compatibility. Consult it for the operations, headers and behaviours it supports. diff --git a/docs/compatibility/s3-api-coverage/intro.md b/docs/compatibility/s3-api-coverage/intro.md deleted file mode 100644 index 5d4edd03c..000000000 --- a/docs/compatibility/s3-api-coverage/intro.md +++ /dev/null @@ -1,7 +0,0 @@ -### Where S3 is served - -Spinifex does not dispatch S3 operations. The REST surface is delegated whole to **Predastore**, which serves it directly, so there is no operation-name dispatch table in the gateway to compare against the model — the comparison that produces every other page on this site has nothing to read here. - -That is why this page reports no percentage. Claiming one from an empty dispatch table would read as 0% implemented, which is the opposite of the truth: S3 is one of the most complete surfaces in the platform. - -Predastore publishes its own measured compatibility. Consult it for the operations, headers and behaviours it supports. diff --git a/docs/compatibility/sts-api-coverage/README.md b/docs/compatibility/sts-api-coverage/README.md index 3f3291b8d..ccd659012 100644 --- a/docs/compatibility/sts-api-coverage/README.md +++ b/docs/compatibility/sts-api-coverage/README.md @@ -32,14 +32,6 @@ Several inputs the model describes are deliberately rejected rather than accepte ### Operations -| Status | Meaning | -|---|---| -| ✅ Implemented | A modelled operation bound to a real handler. | -| 🟡 Stub | A registered handler that answers with a fixed or empty result. | -| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. | -| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. | -| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. | - | Operation | Status | |---|---| | `AssumeRole` | ✅ Implemented | diff --git a/internal/awsmodel/page.go b/internal/awsmodel/page.go index d98c466db..d20284f2b 100644 --- a/internal/awsmodel/page.go +++ b/internal/awsmodel/page.go @@ -108,10 +108,12 @@ func ParsePageSet(contents []byte) (PageSet, error) { if err := validatePage("index", pages.Index, slugs); err != nil { return PageSet{}, err } + // A known service may be left out. S3 is: Predastore serves that surface, + // so the gateway dispatch tables hold no honest number to publish for it. for _, service := range Services() { page, ok := pages.Services[service] if !ok { - return PageSet{}, fmt.Errorf("awsmodel: coverage pages: no entry for service %q", service) + continue } if err := validatePage(string(service), page, slugs); err != nil { return PageSet{}, err @@ -165,23 +167,24 @@ func RenderServicePage(coverage OperationCoverage, pages PageSet, intro string) if !ok { return "", fmt.Errorf("awsmodel: coverage pages: no entry for service %q", coverage.Service) } + // An opaque service has no dispatch table to count, so there is no honest + // number to put on a page. It is left out until a real one exists. + if coverage.Opaque { + return "", fmt.Errorf("awsmodel: %s coverage is not enumerable, so it has no publishable page", coverage.Service) + } var body strings.Builder writeFrontmatter(&body, page, pages.Category) fmt.Fprintf(&body, "# %s\n\n## Overview\n\n", page.Title) - writeSummary(&body, coverage, page) + fmt.Fprintf(&body, "Spinifex implements **%d of the %d** operations in the %s `%s` API model, as pinned in `aws-sdk-go %s` — **%.1f%%**.\n\n", + len(coverage.Implemented), len(coverage.Modelled), page.Name, coverage.APIVersion, SourceSDKVersion, coverage.ImplementedPercent()) if intro != "" { body.WriteString(strings.TrimSpace(intro) + "\n\n") } - if coverage.Opaque { - return strings.TrimRight(body.String(), "\n") + "\n", nil - } - body.WriteString("### Operations\n\n") - writeStatusLegend(&body) - body.WriteString("\n| Operation | Status |\n|---|---|\n") + body.WriteString("### Operations\n\n| Operation | Status |\n|---|---|\n") for _, status := range coverage.OperationStatuses() { fmt.Fprintf(&body, "| `%s` | %s |\n", status.Operation, status.Status) } @@ -202,48 +205,18 @@ func RenderIndexPage(coverages []OperationCoverage, pages PageSet, intro string) for _, coverage := range sortedCoverages(coverages) { page, ok := pages.Services[coverage.Service] if !ok { - return "", fmt.Errorf("awsmodel: coverage pages: no entry for service %q", coverage.Service) - } - if coverage.Opaque { - fmt.Fprintf(&body, "| [%s](/docs/%s) | %s | — | %d | — |\n", page.Name, page.Slug, coverage.APIVersion, len(coverage.Modelled)) continue } fmt.Fprintf(&body, "| [%s](/docs/%s) | %s | %d | %d | %.1f%% |\n", page.Name, page.Slug, coverage.APIVersion, len(coverage.Implemented), len(coverage.Modelled), coverage.ImplementedPercent()) } - body.WriteString("\n") - writeStatusLegend(&body) if intro != "" { body.WriteString("\n" + strings.TrimSpace(intro) + "\n") } return body.String(), nil } -// writeSummary states the implemented-of-modelled count, or why there is none. -func writeSummary(body *strings.Builder, coverage OperationCoverage, page PageMetadata) { - if coverage.Opaque { - fmt.Fprintf(body, "The %s API model pins **%d** operations at API version `%s`, but Spinifex's coverage of them is not mechanically enumerable: %s\n\n", - page.Name, len(coverage.Modelled), coverage.APIVersion, coverage.Note) - return - } - fmt.Fprintf(body, "Spinifex implements **%d of the %d** operations in the %s `%s` API model, as pinned in `aws-sdk-go %s` — **%.1f%%**.\n\n", - len(coverage.Implemented), len(coverage.Modelled), page.Name, coverage.APIVersion, SourceSDKVersion, coverage.ImplementedPercent()) -} - -func writeStatusLegend(body *strings.Builder) { - body.WriteString("| Status | Meaning |\n|---|---|\n") - for _, entry := range [][2]string{ - {StatusImplemented, "A modelled operation bound to a real handler."}, - {StatusStub, "A registered handler that answers with a fixed or empty result."}, - {StatusNotSupported, "A registered handler that deliberately refuses, so a client sees \"not offered\" rather than an unknown action."}, - {StatusNotImplemented, "Modelled by AWS, not registered by Spinifex."}, - {StatusOutsideModel, "Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action."}, - } { - fmt.Fprintf(body, "| %s | %s |\n", entry[0], entry[1]) - } -} - func writeFrontmatter(body *strings.Builder, page PageMetadata, category string) { body.WriteString("---\n") fmt.Fprintf(body, "title: %s\n", strconv.Quote(page.Title)) diff --git a/internal/awsmodel/page_test.go b/internal/awsmodel/page_test.go index ad098b0e9..e0f41d27d 100644 --- a/internal/awsmodel/page_test.go +++ b/internal/awsmodel/page_test.go @@ -1,10 +1,12 @@ -package awsmodel +package awsmodel_test import ( "os" "path/filepath" "strings" "testing" + + . "github.com/mulgadc/spinifex/internal/awsmodel" ) func testPageSet(t *testing.T) PageSet { @@ -23,10 +25,13 @@ func testPageSet(t *testing.T) PageSet { func TestParsePageSetAcceptsTheCheckedInFile(t *testing.T) { pages := testPageSet(t) - for _, service := range Services() { - if _, ok := pages.Services[service]; !ok { - t.Errorf("no page metadata for service %q", service) - } + if len(pages.Services) == 0 { + t.Fatal("no service pages are configured") + } + // S3 is deliberately unpublished: Predastore serves that surface, so the + // gateway dispatch tables hold no honest number for it. + if _, ok := pages.Services[S3]; ok { + t.Error("S3 has page metadata but no enumerable coverage to publish") } } @@ -137,33 +142,28 @@ func TestRenderServicePageRejectsIntroOpeningASection(t *testing.T) { } } -func TestRenderServicePageStatesOpaqueCoverage(t *testing.T) { +// An opaque service has no dispatch table to count, so publishing a page for it +// would report 0% where the truth is that the surface is served elsewhere. +func TestRenderServicePageRefusesOpaqueCoverage(t *testing.T) { pages := testPageSet(t) - coverage, err := CompareOperations(S3, DispatchInventory{ + coverage, err := CompareOperations(STS, DispatchInventory{ Opaque: true, - Note: "Predastore serves this surface.", + Note: "Served elsewhere.", }) if err != nil { t.Fatal(err) } - page, err := RenderServicePage(coverage, pages, "") - if err != nil { - t.Fatal(err) - } - if !strings.Contains(page, "not mechanically enumerable: Predastore serves this surface.") { - t.Errorf("page does not state why coverage is not enumerable:\n%s", page) - } - if strings.Contains(page, "### Operations") || strings.Contains(page, "%**") { - t.Errorf("opaque page claims a percentage or an operation table:\n%s", page) + if _, err := RenderServicePage(coverage, pages, ""); err == nil { + t.Fatal("RenderServicePage published a page for a service with no enumerable coverage") } } -func TestRenderIndexPageLinksEveryService(t *testing.T) { +func TestRenderIndexPageLinksEveryPublishedService(t *testing.T) { pages := testPageSet(t) coverages := make([]OperationCoverage, 0, len(Services())) for _, service := range Services() { - coverage, err := CompareOperations(service, DispatchInventory{Opaque: true, Note: "not measured here."}) + coverage, err := CompareOperations(service, DispatchInventory{}) if err != nil { t.Fatal(err) } @@ -175,7 +175,13 @@ func TestRenderIndexPageLinksEveryService(t *testing.T) { t.Fatal(err) } for _, service := range Services() { - page := pages.Services[service] + page, published := pages.Services[service] + if !published { + if strings.Contains(index, "/docs/"+string(service)+"-api-coverage") { + t.Errorf("index links unpublished service %q", service) + } + continue + } if !strings.Contains(index, "["+page.Name+"](/docs/"+page.Slug+")") { t.Errorf("index does not link %q at /docs/%s", page.Name, page.Slug) }