mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
test: target fixture resources in generated conformance requests
Salts restart per operation, so tag, untag and update cases only reached a resource when the same-numbered create case had made one. Create an IAM, ECS and ECR fixture per run and point every operation but a create or delete at it, and give ECR's policy and manifest documents values AWS accepts.
This commit is contained in:
@@ -75,6 +75,11 @@ func GenerateRequests(service Service, operationName string, options RequestOpti
|
||||
return RequestPlan{Cases: []RequestCase{{Operation: operationName, Input: map[string]any{}}}, DeclaredErrors: declared}, nil
|
||||
}
|
||||
|
||||
// A create or delete names a resource of its own, so it neither collides
|
||||
// with a fixture nor removes one.
|
||||
if strings.HasPrefix(operationName, "Create") || strings.HasPrefix(operationName, "Delete") {
|
||||
options.Fixtures = nil
|
||||
}
|
||||
first := model.newRequestGenerator(0, options)
|
||||
full, ok := first.structure(operation.Input.Shape, nil, 0)
|
||||
plan := RequestPlan{Skipped: first.skipped, Unbroken: first.unbroken, DeclaredErrors: declared}
|
||||
|
||||
@@ -18,10 +18,12 @@ import (
|
||||
|
||||
// RequestOptions places generated ARNs in the account and region the
|
||||
// requests are sent as. AccessKeyID is the key the requests are signed with.
|
||||
// Fixtures maps a member to an existing resource it can name.
|
||||
type RequestOptions struct {
|
||||
AccountID string
|
||||
Region string
|
||||
AccessKeyID string
|
||||
Fixtures map[string]string
|
||||
}
|
||||
|
||||
// Models constrain most identifiers only by length, so "spxa" is a model-valid
|
||||
@@ -82,6 +84,11 @@ var conditionallyRequired = map[Service]map[string][]string{
|
||||
}
|
||||
|
||||
const (
|
||||
// imageManifest is well formed but names blobs no repository holds.
|
||||
imageManifest = `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","size":2,"digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000"},"layers":[]}`
|
||||
lifecyclePolicy = `{"rules":[{"rulePriority":1,"description":"expire untagged","selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countUnit":"days","countNumber":14},"action":{"type":"expire"}}]}`
|
||||
repositoryPolicy = `{"Version":"2012-10-17","Statement":[{"Sid":"pull","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::%s:root"},"Action":"ecr:BatchGetImage"}]}`
|
||||
|
||||
policyDocument = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"*"}]}`
|
||||
trustPolicyDocument = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}`
|
||||
)
|
||||
@@ -94,6 +101,9 @@ func (g *requestGenerator) hint(path []pathStep) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
options := g.options
|
||||
if fixture, ok := options.Fixtures[member]; ok {
|
||||
return fixture, true
|
||||
}
|
||||
suffix := fmt.Sprintf("%017x", g.saltNumber)
|
||||
name := "spx" + g.salt
|
||||
|
||||
@@ -157,8 +167,15 @@ func (g *requestGenerator) hint(path []pathStep) (string, bool) {
|
||||
return name + ".example.com", true
|
||||
}
|
||||
case ECR:
|
||||
if member == "resourceArn" {
|
||||
switch member {
|
||||
case "resourceArn":
|
||||
return fmt.Sprintf("arn:aws:ecr:%s:%s:repository/%s", options.Region, options.AccountID, name), true
|
||||
case "imageManifest":
|
||||
return imageManifest, true
|
||||
case "lifecyclePolicyText":
|
||||
return lifecyclePolicy, true
|
||||
case "policyText":
|
||||
return fmt.Sprintf(repositoryPolicy, options.AccountID), true
|
||||
}
|
||||
case EKS:
|
||||
switch member {
|
||||
|
||||
@@ -226,6 +226,21 @@ func TestGenerateRequestsSeedsConditionallyRequiredMembers(t *testing.T) {
|
||||
require.NotZero(t, nested)
|
||||
}
|
||||
|
||||
func TestGenerateRequestsTargetsFixturesOutsideCreateAndDelete(t *testing.T) {
|
||||
options := testRequestOptions
|
||||
options.Fixtures = map[string]string{"RoleName": "fixture"}
|
||||
for operation, wantFixture := range map[string]bool{"TagRole": true, "CreateRole": false, "DeleteRole": false} {
|
||||
plan, err := GenerateRequests(IAM, operation, options)
|
||||
require.NoError(t, err)
|
||||
for _, request := range plan.Cases {
|
||||
if request.Path == "$.RoleName" {
|
||||
continue
|
||||
}
|
||||
require.Equal(t, wantFixture, request.Input["RoleName"] == "fixture", "%s %s %s", operation, request.Constraint, request.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRequestsAttributesRejections(t *testing.T) {
|
||||
plan, err := GenerateRequests(IAM, "CreateRole", testRequestOptions)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -50,8 +51,10 @@ func TestRequestConformance(t *testing.T) {
|
||||
startRequestConformanceBackends(t, gw)
|
||||
sender, err := newRequestSender(gw, service)
|
||||
require.NoError(t, err)
|
||||
options := awsmodel.RequestOptions{AccountID: gw.AccountID, Region: testRegion, AccessKeyID: testAccessKeyID}
|
||||
options.Fixtures = createRequestFixtures(t, sender, service, gw.AccountID)
|
||||
for _, operation := range coverage.Implemented {
|
||||
plan, err := awsmodel.GenerateRequests(service, operation, awsmodel.RequestOptions{AccountID: gw.AccountID, Region: testRegion, AccessKeyID: testAccessKeyID})
|
||||
plan, err := awsmodel.GenerateRequests(service, operation, options)
|
||||
require.NoError(t, err, operation)
|
||||
suiteRequestConformance.recordOperation(service, operation, plan)
|
||||
judge := plan.NewJudge()
|
||||
@@ -82,6 +85,56 @@ func startRequestConformanceBackends(t *testing.T, gw *Gateway) {
|
||||
StartServiceDaemonLite(t, gw)
|
||||
}
|
||||
|
||||
// requestFixture is the name every fixture resource takes.
|
||||
const requestFixture = "conformance-fixture"
|
||||
|
||||
// createRequestFixtures creates one resource of each kind the service's
|
||||
// generated requests name, so a case reaches its resource instead of ending
|
||||
// in not-found. It returns the members that name them.
|
||||
func createRequestFixtures(t *testing.T, sender *requestSender, service awsmodel.Service, accountID string) map[string]string {
|
||||
t.Helper()
|
||||
create := func(operation string, input map[string]any) {
|
||||
t.Helper()
|
||||
result, err := sender.send(awsmodel.RequestCase{Operation: operation, Input: input})
|
||||
require.NoError(t, err, operation)
|
||||
require.True(t, result.Status >= 200 && result.Status < 300, "create fixture: %s: %d %s: %s", operation, result.Status, result.Code, result.Message)
|
||||
}
|
||||
switch service {
|
||||
case awsmodel.IAM:
|
||||
const oidcHost = "fixture.example.com"
|
||||
create("CreateRole", map[string]any{"RoleName": requestFixture, "AssumeRolePolicyDocument": `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}`})
|
||||
create("CreatePolicy", map[string]any{"PolicyName": requestFixture, "PolicyDocument": `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"*"}]}`})
|
||||
create("CreateOpenIDConnectProvider", map[string]any{"Url": "https://" + oidcHost, "ClientIDList": []any{"sts.amazonaws.com"}, "ThumbprintList": []any{strings.Repeat("a", 40)}})
|
||||
create("CreateInstanceProfile", map[string]any{"InstanceProfileName": requestFixture})
|
||||
create("CreateGroup", map[string]any{"GroupName": requestFixture})
|
||||
create("CreateUser", map[string]any{"UserName": requestFixture})
|
||||
return map[string]string{
|
||||
"RoleName": requestFixture,
|
||||
"PolicyArn": "arn:aws:iam::" + accountID + ":policy/" + requestFixture,
|
||||
"OpenIDConnectProviderArn": "arn:aws:iam::" + accountID + ":oidc-provider/" + oidcHost,
|
||||
"InstanceProfileName": requestFixture,
|
||||
"GroupName": requestFixture,
|
||||
"UserName": requestFixture,
|
||||
}
|
||||
case awsmodel.ECS:
|
||||
create("CreateCluster", map[string]any{"clusterName": requestFixture})
|
||||
return map[string]string{
|
||||
"cluster": requestFixture,
|
||||
"clusters": requestFixture,
|
||||
"resourceArn": "arn:aws:ecs:" + testRegion + ":" + accountID + ":cluster/" + requestFixture,
|
||||
}
|
||||
case awsmodel.ECR:
|
||||
create("CreateRepository", map[string]any{"repositoryName": requestFixture})
|
||||
return map[string]string{
|
||||
"repositoryName": requestFixture,
|
||||
"repositoryNames": requestFixture,
|
||||
"resourceArn": "arn:aws:ecr:" + testRegion + ":" + accountID + ":repository/" + requestFixture,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type requestSender struct {
|
||||
endpoint string
|
||||
service awsmodel.Service
|
||||
|
||||
Reference in New Issue
Block a user