spec

package
v0.0.0-...-3d80a46 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	UserSchema         = "urn:ietf:params:scim:schemas:core:2.0:User"
	GroupSchema        = "urn:ietf:params:scim:schemas:core:2.0:Group"
	TestResourceSchema = "urn:ietf:params:scim:schemas:test:2.0:TestResource"
	TestExtSchema      = "urn:ietf:params:scim:schemas:extension:test:2.0:TestResource"
	PatchOpSchema      = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
	BulkSchema         = "urn:ietf:params:scim:api:messages:2.0:BulkRequest"
)

Variables

All contains every cataloged requirement across all three RFCs.

View Source
var Extra = []Requirement{

	{
		ID:       "EXTRA-TYPE-INT",
		Level:    Must,
		Summary:  "Integer attributes must round-trip without fractional parts",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "integer_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"integerAttr": 42,
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST /TestResources returned %d", resp.StatusCode)
					}

					v, ok := body["integerAttr"].(float64)
					r.Check(ok && v == 42,
						fmt.Sprintf("integerAttr = %v, want 42", body["integerAttr"]))

					id := IDOf(body)
					getResp, err := r.Client.Get("/TestResources/" + id)
					r.RequireOK(err)
					if getResp.StatusCode == 200 {
						gv, _ := getResp.Body["integerAttr"].(float64)
						r.Check(gv == float64(int(gv)),
							fmt.Sprintf("integerAttr on GET = %v, contains fractional part", gv))
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-TYPE-BOOL",
		Level:    Must,
		Summary:  "Boolean attributes must round-trip as JSON true/false",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "boolean_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"booleanAttr": true,
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					v, ok := body["booleanAttr"].(bool)
					r.Check(ok && v,
						fmt.Sprintf("booleanAttr = %v (%T), want true", body["booleanAttr"], body["booleanAttr"]))
				},
			},
		},
	},
	{
		ID:       "EXTRA-TYPE-DT",
		Level:    Must,
		Summary:  "DateTime attributes must round-trip as valid xsd:dateTime strings",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "datetime_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"dateTimeAttr": "2025-06-15T10:30:00Z",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					v, ok := body["dateTimeAttr"].(string)
					r.Check(ok && strings.Contains(v, "T") && (strings.HasSuffix(v, "Z") || len(v) > 19),
						fmt.Sprintf("dateTimeAttr = %q, want valid xsd:dateTime", v))
				},
			},
		},
	},
	{
		ID:       "EXTRA-TYPE-BIN",
		Level:    Must,
		Summary:  "Binary attributes must round-trip as base64-encoded strings",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "binary_round_trip",
				Fn: func(r *Run) {
					raw := []byte("hello, compliance test")
					encoded := base64.StdEncoding.EncodeToString(raw)

					body, resp := r.CreateTestResource(map[string]any{
						"binaryAttr": encoded,
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					v, ok := body["binaryAttr"].(string)
					r.Check(ok && v != "",
						fmt.Sprintf("binaryAttr = %v, want base64 string", body["binaryAttr"]))

					if ok && v != "" {
						_, err := base64.StdEncoding.DecodeString(v)
						r.Check(err == nil,
							fmt.Sprintf("binaryAttr is not valid base64: %v", err))
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-TYPE-REF",
		Level:    Must,
		Summary:  "Reference attributes must round-trip as URI strings",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "reference_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"referenceAttr": "https://example.com/resource/123",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					v, ok := body["referenceAttr"].(string)
					r.Check(ok && v != "",
						fmt.Sprintf("referenceAttr = %v, want URI string", body["referenceAttr"]))

					if ok && v != "" {
						_, err := url.Parse(v)
						r.Check(err == nil,
							fmt.Sprintf("referenceAttr is not a valid URI: %v", err))
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-TYPE-COMPLEX",
		Level:    Must,
		Summary:  "Complex attributes must round-trip with sub-attributes intact",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "complex_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"complexAttr": map[string]any{
							"sub1": "hello",
							"sub2": 99,
							"sub3": true,
						},
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					c, ok := body["complexAttr"].(map[string]any)
					r.Check(ok && c != nil,
						fmt.Sprintf("complexAttr = %v, want object", body["complexAttr"]))

					if ok && c != nil {
						s1, _ := c["sub1"].(string)
						r.Check(s1 == "hello",
							fmt.Sprintf("complexAttr.sub1 = %q, want \"hello\"", s1))

						s2, _ := c["sub2"].(float64)
						r.Check(s2 == 99,
							fmt.Sprintf("complexAttr.sub2 = %v, want 99", c["sub2"]))
					}
				},
			},
		},
	},

	{
		ID:       "EXTRA-MUT-IMMUTABLE",
		Level:    Must,
		Summary:  "Immutable attributes must be accepted at creation and rejected on update",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "immutable_attr",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"immutableAttr": "set-at-creation",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}
					id := IDOf(body)
					identifier, _ := body["identifier"].(string)

					v, _ := body["immutableAttr"].(string)
					r.Check(v == "set-at-creation",
						fmt.Sprintf("immutableAttr = %q after create, want \"set-at-creation\"", v))

					putResp, err := r.Client.Put("/TestResources/"+id, map[string]any{
						"schemas":       []string{TestResourceSchema},
						"identifier":    identifier,
						"immutableAttr": "changed-value",
					})
					r.RequireOK(err)

					if putResp.StatusCode == 200 {
						updated, _ := putResp.Body["immutableAttr"].(string)
						r.Check(updated == "set-at-creation",
							fmt.Sprintf("immutableAttr changed to %q on PUT, should stay immutable", updated))
					} else {
						r.Check(putResp.StatusCode == 400,
							FmtStatus("PUT with changed immutable attr", putResp.StatusCode, 400))
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-MUT-WRITEONLY",
		Level:    Must,
		Summary:  "WriteOnly attributes must be accepted but never returned",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "write_only_attr",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"writeOnlyAttr": "secret-value",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					_, hasWO := body["writeOnlyAttr"]
					r.Check(!hasWO,
						"writeOnlyAttr was returned in POST response")

					id := IDOf(body)
					getResp, err := r.Client.Get("/TestResources/" + id)
					r.RequireOK(err)
					if getResp.StatusCode == 200 {
						_, hasWO = getResp.Body["writeOnlyAttr"]
						r.Check(!hasWO,
							"writeOnlyAttr was returned in GET response")
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-MUT-READONLY",
		Level:    Must,
		Summary:  "ReadOnly attributes provided by the client must be ignored",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "read_only_ignored",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"readOnlyAttr": "client-value",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					v, _ := body["readOnlyAttr"].(string)
					r.Check(v != "client-value",
						fmt.Sprintf("readOnlyAttr = %q, server should have ignored client value", v))
				},
			},
		},
	},

	{
		ID:       "EXTRA-RET-ALWAYS",
		Level:    Must,
		Summary:  "Returned:always attributes must appear even when excluded",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "returned_always",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"alwaysReturnedAttr": "always-here",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}
					id := IDOf(body)

					getResp, err := r.Client.Get("/TestResources/" + id + "?excludedAttributes=alwaysReturnedAttr")
					r.RequireOK(err)

					if getResp.StatusCode == 200 {
						v, has := getResp.Body["alwaysReturnedAttr"]
						r.Check(has && v != nil,
							"returned:always attribute was excluded by excludedAttributes")
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-RET-NEVER",
		Level:    Must,
		Summary:  "Returned:never attributes must never appear in responses",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "returned_never",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"writeOnlyAttr": "secret-value",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					_, hasWO := body["writeOnlyAttr"]
					r.Check(!hasWO,
						"writeOnlyAttr was returned in POST response")

					id := IDOf(body)
					getResp, err := r.Client.Get("/TestResources/" + id)
					r.RequireOK(err)
					if getResp.StatusCode == 200 {
						_, hasWO = getResp.Body["writeOnlyAttr"]
						r.Check(!hasWO,
							"writeOnlyAttr was returned in GET response")
					}
				},
			},
		},
	},

	{
		ID:       "EXTRA-CASE-EXACT",
		Level:    Must,
		Summary:  "Filters on caseExact=true attributes must match case-sensitively",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "filter_case_exact",
				Fn: func(r *Run) {
					_, resp := r.CreateTestResource(map[string]any{
						"caseExactString": "CaSeExAcT",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					filter := "caseExactString eq \"caseexact\""
					qResp, err := r.Client.Get("/TestResources?filter=" + url.QueryEscape(filter))
					r.RequireOK(err)

					if qResp.StatusCode == 200 {
						tr, _ := qResp.Body["totalResults"].(float64)
						r.Check(tr == 0,
							fmt.Sprintf("caseExact filter with wrong case matched %v results, want 0", tr))
					}

					filter = "caseExactString eq \"CaSeExAcT\""
					qResp2, err := r.Client.Get("/TestResources?filter=" + url.QueryEscape(filter))
					r.RequireOK(err)

					if qResp2.StatusCode == 200 {
						tr, _ := qResp2.Body["totalResults"].(float64)
						r.Check(tr >= 1,
							fmt.Sprintf("caseExact filter with correct case matched %v results, want >= 1", tr))
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-CASE-INSENSITIVE",
		Level:    Must,
		Summary:  "Filters on caseExact=false attributes must match case-insensitively",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "filter_case_insensitive",
				Fn: func(r *Run) {
					_, resp := r.CreateTestResource(map[string]any{
						"caseInsensitiveString": "MiXeDcAsE",
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					filter := "caseInsensitiveString eq \"mixedcase\""
					qResp, err := r.Client.Get("/TestResources?filter=" + url.QueryEscape(filter))
					r.RequireOK(err)

					if qResp.StatusCode == 200 {
						tr, _ := qResp.Body["totalResults"].(float64)
						r.Check(tr >= 1,
							fmt.Sprintf("case-insensitive filter matched %v results, want >= 1", tr))
					}
				},
			},
		},
	},

	{
		ID:       "EXTRA-EXT-CONTAINER",
		Level:    Must,
		Summary:  "Extension attributes must be namespaced under the extension schema URI",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "extension_container",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"schemas": []string{TestResourceSchema, TestExtSchema},
						TestExtSchema: map[string]any{
							"extString":   "ext-value",
							"extRequired": "required-value",
						},
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					extData, ok := body[TestExtSchema].(map[string]any)
					r.Check(ok && extData != nil,
						fmt.Sprintf("extension data not found under key %q", TestExtSchema))
				},
			},
		},
	},
	{
		ID:       "EXTRA-EXT-ROUNDTRIP",
		Level:    Must,
		Summary:  "Extension attributes must round-trip through create and get",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "extension_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"schemas": []string{TestResourceSchema, TestExtSchema},
						TestExtSchema: map[string]any{
							"extString":   "ext-value",
							"extRequired": "required-value",
						},
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					extData, ok := body[TestExtSchema].(map[string]any)
					if ok && extData != nil {
						es, _ := extData["extString"].(string)
						r.Check(es == "ext-value",
							fmt.Sprintf("extString = %q, want \"ext-value\"", es))

						er, _ := extData["extRequired"].(string)
						r.Check(er == "required-value",
							fmt.Sprintf("extRequired = %q, want \"required-value\"", er))
					}

					id := IDOf(body)
					getResp, err := r.Client.Get("/TestResources/" + id)
					r.RequireOK(err)

					if getResp.StatusCode == 200 {
						extGet, ok := getResp.Body[TestExtSchema].(map[string]any)
						r.Check(ok && extGet != nil,
							"GET: extension data missing after creation")
					}
				},
			},
		},
	},
	{
		ID:       "EXTRA-EXT-SCHEMAS",
		Level:    Must,
		Summary:  "Resources with extensions must include extension URI in schemas array",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "extension_schemas",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"schemas": []string{TestResourceSchema, TestExtSchema},
						TestExtSchema: map[string]any{
							"extString":   "ext-value",
							"extRequired": "required-value",
						},
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					schemas := GetStringSlice(body, "schemas")
					r.Check(HasSchema(body, TestExtSchema),
						fmt.Sprintf("schemas = %v, missing extension URI %s", schemas, TestExtSchema))
				},
			},
		},
	},

	{
		ID:       "EXTRA-MV-ROUNDTRIP",
		Level:    Must,
		Summary:  "Multi-valued attributes must round-trip preserving all values",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "multi_valued_round_trip",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"multiStrings": []string{"alpha", "beta", "gamma"},
					})
					if resp.StatusCode != 201 {
						r.Fatalf("setup: POST returned %d", resp.StatusCode)
					}

					arr, ok := body["multiStrings"].([]any)
					r.Check(ok && len(arr) == 3,
						fmt.Sprintf("multiStrings = %v, want 3 elements", body["multiStrings"]))
				},
			},
		},
	},
	{
		ID:       "EXTRA-MV-COMPLEX-PRIMARY",
		Level:    Must,
		Summary:  "Multi-valued complex attributes must enforce at most one primary=true",
		Feature:  TestResource,
		Testable: true,
		Tests: []Test{
			{
				Name: "multi_complex_primary",
				Fn: func(r *Run) {
					body, resp := r.CreateTestResource(map[string]any{
						"multiComplex": []map[string]any{
							{"value": "one", "type": "a", "primary": true},
							{"value": "two", "type": "b", "primary": true},
						},
					})
					if resp.StatusCode == 201 {
						mc, _ := body["multiComplex"].([]any)
						primaryCount := 0
						for _, item := range mc {
							m, ok := item.(map[string]any)
							if !ok {
								continue
							}
							if p, _ := m["primary"].(bool); p {
								primaryCount++
							}
						}
						r.Check(primaryCount <= 1,
							fmt.Sprintf("multiComplex has %d primary=true, want <= 1", primaryCount))
					} else if resp.StatusCode == 400 {

						r.Check(true, "")
					} else {
						r.Check(false,
							FmtStatus("POST /TestResources with duplicate primary", resp.StatusCode, 400))
					}
				},
			},
		},
	},
}

Extra contains additional compliance checks that go beyond the literal RFC requirements. These exercise attribute type combinations, extension handling, and edge cases that the standard User/Group schemas do not cover.

View Source
var RFC7642 = []Requirement{
	{
		ID:      "RFC7642-4-L944",
		Level:   Must,
		Summary: "Transport layer must guarantee data confidentiality (TLS)",
		Source: Source{
			RFC:       7642,
			Section:   "4",
			StartLine: 943,
			EndLine:   945,
		},
		Feature:  Core,
		Testable: false,
	},
}
View Source
var RFC7643 = concat(
	rfc7643_1_1,
	rfc7643_2_1,
	rfc7643_2_3,
	rfc7643_2_4,
	rfc7643_2_5,
	rfc7643_3,
	rfc7643_3_1,
	rfc7643_3_3,
	rfc7643_4_1,
	rfc7643_5,
	rfc7643_6,
	rfc7643_7,
	rfc7643_9,
)

RFC7643 contains requirements from RFC 7643.

View Source
var RFC7644 = concat(
	rfc7644_1_3,
	rfc7644_2,
	rfc7644_3_1,
	rfc7644_3_2,
	rfc7644_3_3,
	rfc7644_3_4,
	rfc7644_3_5,
	rfc7644_3_6,
	rfc7644_3_7,
	rfc7644_3_8,
	rfc7644_3_9,
	rfc7644_3_11,
	rfc7644_3_12,
	rfc7644_3_13,
	rfc7644_3_14,
	rfc7644_4,
	rfc7644_5,
	rfc7644_7,
)

RFC7644 contains requirements from RFC 7644.

Functions

func FmtStatus

func FmtStatus(what string, got, want int) string

FmtStatus formats a status code mismatch message.

func GetString

func GetString(m map[string]any, keys ...string) string

GetString extracts a nested string from a JSON map.

func GetStringSlice

func GetStringSlice(m map[string]any, key string) []string

GetStringSlice extracts a []string from a JSON interface slice.

func HasSchema

func HasSchema(body map[string]any, uri string) bool

HasSchema checks if the schemas array contains the given URI.

func IDOf

func IDOf(body map[string]any) string

IDOf extracts the "id" field from a SCIM resource body.

func IsBase64

func IsBase64(s string) bool

IsBase64 checks if a string is valid base64.

func IsSchemaURI

func IsSchemaURI(s string) bool

IsSchemaURI returns true if the key is a SCIM schema URI (extension container key), not a regular attribute name.

func IsValidAttrName

func IsValidAttrName(s string) bool

IsValidAttrName checks if a string conforms to the SCIM ATTRNAME ABNF: ALPHA *(nameChar) where nameChar = "-" / "_" / DIGIT / ALPHA.

func RFCText

func RFCText(rfc int) (string, error)

RFCText returns the full text of the given RFC, reassembled from per-section files. The result is byte-identical to the original monolithic file.

func RandomSuffix

func RandomSuffix() string

RandomSuffix returns a short random hex string for unique test data.

func SectionFile

func SectionFile(rfc, startLine, endLine int) (path string, localStart, localEnd int)

SectionFile returns the testdata file path and local line numbers for a given RFC and global line range. This is used to generate source links that point to the correct section file.

Types

type DiscoveredExtension

type DiscoveredExtension struct {
	Schema   schema.Schema
	Required bool
}

DiscoveredExtension holds a parsed extension schema and whether it is required.

type DiscoveredResourceType

type DiscoveredResourceType struct {
	Name       string
	Endpoint   string
	Schema     schema.Schema
	Extensions []DiscoveredExtension
}

DiscoveredResourceType holds a resource type and its parsed schemas, as fetched from /ResourceTypes and /Schemas.

type Feature

type Feature string

Feature groups requirements by optional server capability. Tests for a feature are skipped when the feature is not supported.

const (
	// Core is always tested (required endpoints, basic CRUD, errors).
	Core Feature = "core"

	// Discovered via /ServiceProviderConfig.
	Filter         Feature = "filter"
	Sort           Feature = "sort"
	ChangePassword Feature = "changePassword"
	Patch          Feature = "patch"
	Bulk           Feature = "bulk"
	ETag           Feature = "etag"

	// Resource types discovered via /ResourceTypes.
	Users  Feature = "users"
	Groups Feature = "groups"

	// Custom test resource for exercising all attribute types.
	TestResource Feature = "testResource"

	// The /Me endpoint alias.
	Me Feature = "me"
)

type Level

type Level int

Level represents an RFC 2119 compliance keyword.

const (
	Must Level = iota
	MustNot
	Shall
	ShallNot
	Should
	ShouldNot
	May
)

func (Level) String

func (l Level) String() string

type Requirement

type Requirement struct {
	// ID is a stable identifier.
	// Format: RFC{num}-{section}-L{line}, where {line} is the line
	// containing the RFC 2119 keyword. This may differ from
	// Source.StartLine when the sentence begins on an earlier line.
	ID string

	// Level is the RFC 2119 compliance level.
	Level Level

	// Summary is a short, test-oriented description of what to verify.
	Summary string

	// Source is the location in the RFC txt file.
	Source Source

	// Feature determines when this requirement is tested.
	Feature Feature

	// Testable indicates whether this can be verified black-box.
	// Some requirements (e.g. internal password hashing) are not
	// externally observable and are marked false.
	Testable bool

	// Tests contains the compliance tests for this requirement.
	Tests []Test
}

Requirement is a single testable statement from the SCIM RFCs.

func ByFeature

func ByFeature(f Feature) []Requirement

ByFeature returns all requirements that belong to the given feature.

func ByID

func ByID(id string) *Requirement

ByID returns the requirement with the given ID, or nil if not found.

func ByLevel

func ByLevel(l Level) []Requirement

ByLevel returns all requirements at the given compliance level.

type Run

type Run struct {
	Client   *scim.Client
	Features *scim.Features
	// contains filtered or unexported fields
}

Run provides assertions, HTTP helpers, and lifecycle management for a single compliance test. It carries the SCIM client so that test functions defined in spec/ can make HTTP calls without importing any other package.

func (*Run) Check

func (r *Run) Check(ok bool, msg string)

Check asserts a condition. When ok is false, Errorf is called.

func (*Run) Cleanup

func (r *Run) Cleanup(fn func())

Cleanup registers a function to be called after the test completes.

func (*Run) CreateFuzzedResource

func (r *Run) CreateFuzzedResource(rt DiscoveredResourceType, opts ...fuzz.Option) (map[string]any, *scim.Response)

CreateFuzzedResource generates a resource from the schema using the fuzzer, POSTs it to the endpoint, and registers cleanup.

func (*Run) CreateGroup

func (r *Run) CreateGroup(members []map[string]any) (map[string]any, *scim.Response)

CreateGroup creates a Group and registers cleanup to delete it.

func (*Run) CreateTestResource

func (r *Run) CreateTestResource(extra map[string]any) (map[string]any, *scim.Response)

CreateTestResource creates a TestResource and registers cleanup.

func (*Run) CreateUser

func (r *Run) CreateUser() (map[string]any, *scim.Response)

CreateUser creates a test user and registers cleanup to delete it.

func (*Run) DiscoverResourceTypes

func (r *Run) DiscoverResourceTypes() []DiscoveredResourceType

DiscoverResourceTypes fetches /ResourceTypes and /Schemas, parses the schemas, and returns structured resource type definitions.

func (*Run) DoWithHeaders

func (r *Run) DoWithHeaders(method, path string, body map[string]any, headers map[string]string) *scim.Response

DoWithHeaders executes an HTTP request with additional headers.

func (*Run) Errorf

func (r *Run) Errorf(format string, args ...any)

Errorf records a test failure with a formatted message.

func (*Run) Execute

func (r *Run) Execute(fn func(r *Run))

Execute runs a test function, recovering from Fatalf/Skipf panics and running cleanup functions afterward.

func (*Run) Failed

func (r *Run) Failed() bool

Failed reports whether the test has failed.

func (*Run) Fatalf

func (r *Run) Fatalf(format string, args ...any)

Fatalf records a test failure and stops execution immediately.

func (*Run) GenerateSelfSignedCertB64

func (r *Run) GenerateSelfSignedCertB64() string

GenerateSelfSignedCertB64 creates a self-signed X.509 certificate and returns it as a base64-encoded DER string.

func (*Run) Logf

func (r *Run) Logf(format string, args ...any)

Logf records an informational message.

func (*Run) Logs

func (r *Run) Logs() []string

Logs returns all informational messages recorded during the test.

func (*Run) Messages

func (r *Run) Messages() []string

Messages returns all error messages recorded during the test.

func (*Run) RawClient

func (r *Run) RawClient() *scim.Client

RawClient returns a Client with no authentication, for testing unauthenticated access.

func (*Run) RequireOK

func (r *Run) RequireOK(err error)

RequireOK fails the test immediately if err is non-nil.

func (*Run) RunCleanups

func (r *Run) RunCleanups()

RunCleanups executes all registered cleanup functions in LIFO order.

func (*Run) Skipf

func (r *Run) Skipf(format string, args ...any)

Skipf marks the test as skipped and stops execution.

func (*Run) Skipped

func (r *Run) Skipped() bool

Skipped reports whether the test was skipped.

func (*Run) SubResults

func (r *Run) SubResults() []SubResult

SubResults returns the collected subtest results.

func (*Run) Subtest

func (r *Run) Subtest(name string, fn func(r *Run))

Subtest runs fn as a named subtest. Each subtest gets its own pass/fail/skip state and cleanup scope. Results are collected on the parent Run and can be expanded by the compliance runner.

type Source

type Source struct {
	// RFC number: 7642, 7643, or 7644.
	RFC int
	// Section identifier, e.g. "3.5.1".
	Section string
	// StartLine is the starting line number in the plain-text RFC file.
	StartLine int
	// EndLine is the ending line number (inclusive). Equal to StartLine
	// when the requirement fits on a single line.
	EndLine int
	// StartCol is the 1-based column where highlighting begins on
	// StartLine. Zero means highlight from the beginning of the line.
	StartCol int
	// EndCol is the 1-based column where highlighting ends (inclusive)
	// on EndLine. Zero means highlight to the end of the line.
	EndCol int
}

Source pinpoints where a requirement comes from in the RFC txt.

type SubResult

type SubResult struct {
	Name    string
	Failed  bool
	Skipped bool
	Msgs    []string
	Logs    []string
}

SubResult holds the outcome of a single subtest.

type Test

type Test struct {
	// Name is a short identifier for the test (e.g. "eq_match").
	Name string

	// Fn is the test function. It receives a *Run for assertions
	// and lifecycle management.
	Fn func(r *Run)
}

Test is a single compliance test for a requirement.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL