From 7ad9481ae041c71a8b0c5b9354f9c83e4fc929d6 Mon Sep 17 00:00:00 2001 From: Sneha Gunta Date: Fri, 1 May 2026 18:40:54 -0400 Subject: [PATCH] RHCLOUD-45005: Use ResourceType and ReporterType tiny types in schema and events Replace raw string usage of resource type and reporter type with domain-specific tiny types throughout the schema repository, schema service, resource events, outbox events, and related tests. - Update SchemaRepository interface and InMemorySchemaRepository to use ResourceType/ReporterType parameters and return types - Update ResourceEvent interface to return ResourceType/ReporterType - Update SchemaService to accept ResourceType/ReporterType - Update resource_service validation to pass types directly - Add NormalizeReporterType matching existing NormalizeResourceType - Convert loadResourceSchema/loadCommonResourceDataSchema to accept tiny types - Update cmd/schema to use proper constructors for both types - Use .String() at external boundaries (event serialization, map keys, file paths, error messages) Co-authored-by: Cursor --- cmd/schema/schema.go | 25 +- internal/biz/model/resource_delete_event.go | 8 +- internal/biz/model/resource_event.go | 4 +- internal/biz/model/resource_report_event.go | 8 +- internal/biz/model/schema_repository.go | 18 +- internal/biz/model/schema_service.go | 18 +- internal/biz/model_legacy/outboxevents.go | 8 +- .../biz/model_legacy/outboxevents_test.go | 8 +- .../biz/usecase/resources/resource_service.go | 18 +- .../resources/resource_service_test.go | 72 ++--- internal/data/schema_inmemory.go | 123 +++---- internal/data/schema_inmemory_test.go | 299 ++++++++++++------ .../resources/kesselinventoryservice_test.go | 33 +- 13 files changed, 380 insertions(+), 262 deletions(-) diff --git a/cmd/schema/schema.go b/cmd/schema/schema.go index 7d904ba0c..898ae56a8 100644 --- a/cmd/schema/schema.go +++ b/cmd/schema/schema.go @@ -8,10 +8,12 @@ import ( "path/filepath" "github.com/go-kratos/kratos/v2/log" - "github.com/project-kessel/inventory-api/cmd/common" - "github.com/project-kessel/inventory-api/internal/data" "github.com/spf13/cobra" "gopkg.in/yaml.v3" + + "github.com/project-kessel/inventory-api/cmd/common" + bizmodel "github.com/project-kessel/inventory-api/internal/biz/model" + "github.com/project-kessel/inventory-api/internal/data" ) var schemaDir = "data/schema/resources" @@ -40,8 +42,11 @@ func normalizeYAMLResourceType(yamlContent []byte) ([]byte, error) { // Normalize the resources type if it exists if resourceType, exists := yamlData["type"].(string); exists { - normalized := data.NormalizeResourceType(resourceType) - yamlData["type"] = normalized + rt, err := bizmodel.NewResourceType(resourceType) + if err != nil { + return nil, fmt.Errorf("invalid resource type in YAML: %w", err) + } + yamlData["type"] = data.NormalizeResourceType(rt).String() } // Convert back to YAML format @@ -82,7 +87,11 @@ func preloadSchemas() error { continue } - resourceType := data.NormalizeResourceType(resource.Name()) + rt, err := bizmodel.NewResourceType(resource.Name()) + if err != nil { + continue + } + resourceType := data.NormalizeResourceType(rt).String() resourcePath := filepath.Join(schemaDir, resource.Name()) // Load common resource data schema @@ -109,7 +118,11 @@ func preloadSchemas() error { continue } - reporterType := data.NormalizeResourceType(reporter.Name()) + rpt, err := bizmodel.NewReporterType(reporter.Name()) + if err != nil { + continue + } + reporterType := data.NormalizeReporterType(rpt).String() reporterPath := filepath.Join(reportersPath, reporter.Name()) // Encode reporter's config.yaml diff --git a/internal/biz/model/resource_delete_event.go b/internal/biz/model/resource_delete_event.go index 5c946b8b5..2900976b9 100644 --- a/internal/biz/model/resource_delete_event.go +++ b/internal/biz/model/resource_delete_event.go @@ -43,12 +43,12 @@ func (re ResourceDeleteEvent) UpdatedAt() *time.Time { return &re.updatedAt } -func (re ResourceDeleteEvent) ResourceType() string { - return re.resourceType.String() +func (re ResourceDeleteEvent) ResourceType() ResourceType { + return re.resourceType } -func (re ResourceDeleteEvent) ReporterType() string { - return re.reporterId.reporterType.String() +func (re ResourceDeleteEvent) ReporterType() ReporterType { + return re.reporterId.reporterType } func (re ResourceDeleteEvent) ReporterInstanceId() string { diff --git a/internal/biz/model/resource_event.go b/internal/biz/model/resource_event.go index 3f0133cde..ac0d5c9cc 100644 --- a/internal/biz/model/resource_event.go +++ b/internal/biz/model/resource_event.go @@ -2,8 +2,8 @@ package model type ResourceEvent interface { Id() ResourceId - ResourceType() string - ReporterType() string + ResourceType() ResourceType + ReporterType() ReporterType ReporterInstanceId() string LocalResourceId() string WorkspaceId() *string diff --git a/internal/biz/model/resource_report_event.go b/internal/biz/model/resource_report_event.go index 238804bae..fe432e2a6 100644 --- a/internal/biz/model/resource_report_event.go +++ b/internal/biz/model/resource_report_event.go @@ -58,12 +58,12 @@ func (re ResourceReportEvent) UpdatedAt() *time.Time { return &re.updatedAt } -func (re ResourceReportEvent) ResourceType() string { - return re.resourceType.String() +func (re ResourceReportEvent) ResourceType() ResourceType { + return re.resourceType } -func (re ResourceReportEvent) ReporterType() string { - return re.reporterId.reporterType.String() +func (re ResourceReportEvent) ReporterType() ReporterType { + return re.reporterId.reporterType } func (re ResourceReportEvent) ReporterInstanceId() string { diff --git a/internal/biz/model/schema_repository.go b/internal/biz/model/schema_repository.go index dfa62a946..b817b357f 100644 --- a/internal/biz/model/schema_repository.go +++ b/internal/biz/model/schema_repository.go @@ -16,33 +16,33 @@ const ( ) type ResourceSchema struct { - ResourceType string + ResourceType ResourceType ValidationSchema ValidationSchema } type ReporterSchema struct { - ResourceType string - ReporterType string + ResourceType ResourceType + ReporterType ReporterType ValidationSchema ValidationSchema } type SchemaRepository interface { // GetResourceSchemas returns all the resourceTypes that have a ResourceSchema. - GetResourceSchemas(ctx context.Context) ([]string, error) + GetResourceSchemas(ctx context.Context) ([]ResourceType, error) // CreateResourceSchema adds the ResourceSchema into the repository. CreateResourceSchema(ctx context.Context, resource ResourceSchema) error // GetResourceSchema returns the resource schema for the resourceType. Returns ResourceSchemaNotFound if the resource schema does not exist. - GetResourceSchema(ctx context.Context, resourceType string) (ResourceSchema, error) + GetResourceSchema(ctx context.Context, resourceType ResourceType) (ResourceSchema, error) // UpdateResourceSchema updates the ResourceSchema for the resourceType. Returns ResourceSchemaNotFound if the resource schema does not exist. UpdateResourceSchema(ctx context.Context, resource ResourceSchema) error // DeleteResourceSchema deletes the ResourceSchema for the resourceType. Returns ResourceSchemaNotFound if the resource schema does not exist. - DeleteResourceSchema(ctx context.Context, resourceType string) error + DeleteResourceSchema(ctx context.Context, resourceType ResourceType) error // GetReporterSchemas returns all the reporterTypes for resourceType. Returns ResourceSchemaNotFound if the resourceType does not exist. - GetReporterSchemas(ctx context.Context, resourceType string) ([]string, error) + GetReporterSchemas(ctx context.Context, resourceType ResourceType) ([]ReporterType, error) // CreateReporterSchema adds the ReporterSchema into the repository. Returns ResourceSchemaNotFound if the resourceType does not exist. CreateReporterSchema(ctx context.Context, resourceReporter ReporterSchema) error // GetReporterSchema returns the ReporterSchema for the resourceType and reporterType. Returns ResourceSchemaNotFound if the resource schema does not exist and ReporterSchemaNotFound if the reporter schema does not exist for that resource. - GetReporterSchema(ctx context.Context, resourceType string, reporterType string) (ReporterSchema, error) + GetReporterSchema(ctx context.Context, resourceType ResourceType, reporterType ReporterType) (ReporterSchema, error) // UpdateReporterSchema updates the ReporterSchema for the resourceType and reporterType. Returns ResourceSchemaNotFound if the resource schema does not exist and ReporterSchemaNotFound if the reporter schema does not exist for that resource. UpdateReporterSchema(ctx context.Context, resourceReporter ReporterSchema) error // DeleteReporterSchema deletes the ReporterSchema for the resourceType and reporterType. Returns ResourceSchemaNotFound if the resource schema does not exist and ReporterSchemaNotFound if the reporter schema does not exist for that resource. - DeleteReporterSchema(ctx context.Context, resourceType string, reporterType string) error + DeleteReporterSchema(ctx context.Context, resourceType ResourceType, reporterType ReporterType) error } diff --git a/internal/biz/model/schema_service.go b/internal/biz/model/schema_service.go index 5e2f7a2a4..0bf078e05 100644 --- a/internal/biz/model/schema_service.go +++ b/internal/biz/model/schema_service.go @@ -52,7 +52,7 @@ func (sc *SchemaService) CalculateTuples(currentRepresentation, previousRepresen } // IsReporterForResource validates the resourceType and reporterType combination is valid. i.e. that there is a reporter that reports said resource. -func (sc *SchemaService) IsReporterForResource(ctx context.Context, resourceType string, reporterType string) (bool, error) { +func (sc *SchemaService) IsReporterForResource(ctx context.Context, resourceType ResourceType, reporterType ReporterType) (bool, error) { if _, err := sc.schemaRepository.GetReporterSchema(ctx, resourceType, reporterType); err != nil { if errors.Is(err, ResourceSchemaNotFound) || errors.Is(err, ReporterSchemaNotFound) { return false, nil @@ -65,14 +65,14 @@ func (sc *SchemaService) IsReporterForResource(ctx context.Context, resourceType } // CommonShallowValidate validates the common representation for a given resourceType. -func (sc *SchemaService) CommonShallowValidate(ctx context.Context, resourceType string, commonRepresentation map[string]interface{}) error { +func (sc *SchemaService) CommonShallowValidate(ctx context.Context, resourceType ResourceType, commonRepresentation map[string]interface{}) error { resource, err := sc.schemaRepository.GetResourceSchema(ctx, resourceType) if err != nil { - return fmt.Errorf("failed to load common representation schema for '%s': %w", resourceType, err) + return fmt.Errorf("failed to load common representation schema for '%s': %w", resourceType.String(), err) } if resource.ValidationSchema == nil { - return fmt.Errorf("no schema found for '%s'", resourceType) + return fmt.Errorf("no schema found for '%s'", resourceType.String()) } hasCommonRepresentationData := len(commonRepresentation) > 0 @@ -85,14 +85,14 @@ func (sc *SchemaService) CommonShallowValidate(ctx context.Context, resourceType if hasCommonRepresentationData { return err } - return fmt.Errorf("missing 'common' field in payload - schema for '%s' has required fields: %w", resourceType, err) + return fmt.Errorf("missing 'common' field in payload - schema for '%s' has required fields: %w", resourceType.String(), err) } return nil } // ReporterShallowValidate validates the specific reporter representation for a given resourceType/reporterType. -func (sc *SchemaService) ReporterShallowValidate(ctx context.Context, resourceType string, reporterType string, reporterRepresentation map[string]interface{}) error { +func (sc *SchemaService) ReporterShallowValidate(ctx context.Context, resourceType ResourceType, reporterType ReporterType, reporterRepresentation map[string]interface{}) error { reporter, err := sc.schemaRepository.GetReporterSchema(ctx, resourceType, reporterType) if err != nil { return err @@ -101,9 +101,9 @@ func (sc *SchemaService) ReporterShallowValidate(ctx context.Context, resourceTy // Case 1: No schema found for resourceType:reporterType if reporter.ValidationSchema == nil { if len(reporterRepresentation) > 0 { - return fmt.Errorf("no schema found for '%s:%s', but reporter representation was provided. Submission is not allowed", resourceType, reporterType) + return fmt.Errorf("no schema found for '%s:%s', but reporter representation was provided. Submission is not allowed", resourceType.String(), reporterType.String()) } - sc.Log.Debugf("no schema found for %s:%s, treating as abstract reporter representation", resourceType, reporterType) + sc.Log.Debugf("no schema found for %s:%s, treating as abstract reporter representation", resourceType.String(), reporterType.String()) return nil } @@ -119,7 +119,7 @@ func (sc *SchemaService) ReporterShallowValidate(ctx context.Context, resourceTy } // If schema has validation errors but reporterRepresentation is nil/empty, that's an error - return fmt.Errorf("missing 'reporter' field in payload - schema for '%s:%s' has required fields: %w", resourceType, reporterType, err) + return fmt.Errorf("missing 'reporter' field in payload - schema for '%s:%s' has required fields: %w", resourceType.String(), reporterType.String(), err) } return nil diff --git a/internal/biz/model_legacy/outboxevents.go b/internal/biz/model_legacy/outboxevents.go index 062cbf3a2..074064947 100644 --- a/internal/biz/model_legacy/outboxevents.go +++ b/internal/biz/model_legacy/outboxevents.go @@ -132,16 +132,16 @@ func newResourceEvent(operationType bizmodel.EventOperationType, resourceEvent * return &ResourceEvent{ Specversion: "1.0", - Type: makeEventType(eventType, resourceEvent.ResourceType(), string(operationType.OperationType())), + Type: makeEventType(eventType, resourceEvent.ResourceType().String(), string(operationType.OperationType())), Source: "", // TODO: inventory uri Id: eventId.String(), - Subject: makeEventSubject(eventType, resourceEvent.ResourceType(), resourceEvent.Id().String()), + Subject: makeEventSubject(eventType, resourceEvent.ResourceType().String(), resourceEvent.Id().String()), Time: reportedTime, DataContentType: "application/json", Data: EventResourceData{ Metadata: EventResourceMetadata{ Id: resourceEvent.Id().String(), - ResourceType: resourceEvent.ResourceType(), + ResourceType: resourceEvent.ResourceType().String(), CreatedAt: createdAt, UpdatedAt: updatedAt, DeletedAt: deletedAt, @@ -149,7 +149,7 @@ func newResourceEvent(operationType bizmodel.EventOperationType, resourceEvent * }, ReporterData: EventResourceReporter{ ReporterInstanceId: resourceEvent.ReporterInstanceId(), - ReporterType: resourceEvent.ReporterType(), + ReporterType: resourceEvent.ReporterType().String(), ConsoleHref: bizmodel.SerializeStringPtr(resourceEvent.ConsoleHref()), ApiHref: resourceEvent.ApiHref(), LocalResourceId: resourceEvent.LocalResourceId(), diff --git a/internal/biz/model_legacy/outboxevents_test.go b/internal/biz/model_legacy/outboxevents_test.go index 3c3ab91a3..229774e0b 100644 --- a/internal/biz/model_legacy/outboxevents_test.go +++ b/internal/biz/model_legacy/outboxevents_test.go @@ -113,8 +113,8 @@ func assertTupleEventFromDomainEvent(t *testing.T, resourceEvent bizmodel.Resour assert.Nil(t, err) key := tupleEvent.ReporterResourceKey() - assert.Equal(t, resourceEvent.ResourceType(), key.ResourceType().String()) - assert.Equal(t, resourceEvent.ReporterType(), key.ReporterType().String()) + assert.Equal(t, resourceEvent.ResourceType(), key.ResourceType()) + assert.Equal(t, resourceEvent.ReporterType(), key.ReporterType()) assert.Equal(t, resourceEvent.ReporterInstanceId(), key.ReporterInstanceId().String()) assert.Equal(t, resourceEvent.LocalResourceId(), key.LocalResourceId().String()) @@ -146,10 +146,10 @@ func assertResourceEventFromDomainEvent(t *testing.T, operation bizmodel.EventOp var data EventResourceData err = json.Unmarshal(dataBytes, &data) assert.Nil(t, err) - assert.Equal(t, resourceEvent.ResourceType(), data.Metadata.ResourceType) + assert.Equal(t, resourceEvent.ResourceType().String(), data.Metadata.ResourceType) assert.Equal(t, resourceEvent.WorkspaceId(), data.Metadata.WorkspaceId) assert.Equal(t, resourceEvent.ReporterInstanceId(), data.ReporterData.ReporterInstanceId) - assert.Equal(t, resourceEvent.ReporterType(), data.ReporterData.ReporterType) + assert.Equal(t, resourceEvent.ReporterType().String(), data.ReporterData.ReporterType) assert.Equal(t, bizmodel.SerializeStringPtr(resourceEvent.ConsoleHref()), data.ReporterData.ConsoleHref) assert.Equal(t, resourceEvent.ApiHref(), data.ReporterData.ApiHref) assert.Equal(t, resourceEvent.LocalResourceId(), data.ReporterData.LocalResourceId) diff --git a/internal/biz/usecase/resources/resource_service.go b/internal/biz/usecase/resources/resource_service.go index 28f7e9708..633ca0358 100644 --- a/internal/biz/usecase/resources/resource_service.go +++ b/internal/biz/usecase/resources/resource_service.go @@ -588,33 +588,23 @@ func (uc *Usecase) enforceMetaAuthzObject(ctx context.Context, relation metaauth // It checks that the reporter is allowed for the resource type, // and validates both reporter and common representations. func (uc *Usecase) validateReportResourceCommand(ctx context.Context, cmd ReportResourceCommand) error { - resourceType := cmd.ResourceType.String() - reporterType := cmd.ReporterType.String() - - if resourceType == "" { - return fmt.Errorf("missing 'type' field") - } - if reporterType == "" { - return fmt.Errorf("missing 'reporterType' field") - } - - if isReporter, err := uc.schemaService.IsReporterForResource(ctx, resourceType, reporterType); !isReporter { + if isReporter, err := uc.schemaService.IsReporterForResource(ctx, cmd.ResourceType, cmd.ReporterType); !isReporter { if err != nil { return err } - return fmt.Errorf("reporter %s does not report resource types: %s", reporterType, resourceType) + return fmt.Errorf("reporter %s does not report resource types: %s", cmd.ReporterType.String(), cmd.ResourceType.String()) } if cmd.ReporterRepresentation != nil { sanitizedReporterRepresentation := removeNulls(map[string]interface{}(*cmd.ReporterRepresentation)) - if err := uc.schemaService.ReporterShallowValidate(ctx, resourceType, reporterType, sanitizedReporterRepresentation); err != nil { + if err := uc.schemaService.ReporterShallowValidate(ctx, cmd.ResourceType, cmd.ReporterType, sanitizedReporterRepresentation); err != nil { return err } } if cmd.CommonRepresentation != nil { commonRepresentation := map[string]interface{}(*cmd.CommonRepresentation) - if err := uc.schemaService.CommonShallowValidate(ctx, resourceType, commonRepresentation); err != nil { + if err := uc.schemaService.CommonShallowValidate(ctx, cmd.ResourceType, commonRepresentation); err != nil { return err } } diff --git a/internal/biz/usecase/resources/resource_service_test.go b/internal/biz/usecase/resources/resource_service_test.go index 40784aead..cd552af45 100644 --- a/internal/biz/usecase/resources/resource_service_test.go +++ b/internal/biz/usecase/resources/resource_service_test.go @@ -458,28 +458,37 @@ func newFakeSchemaRepository(t *testing.T) model.SchemaRepository { "required": ["workspace_id"] }`) - err := schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ - ResourceType: "k8s_cluster", + k8sCluster, err := model.NewResourceType("k8s_cluster") + require.NoError(t, err) + host, err := model.NewResourceType("host") + require.NoError(t, err) + ocm, err := model.NewReporterType("ocm") + require.NoError(t, err) + hbi, err := model.NewReporterType("hbi") + require.NoError(t, err) + + err = schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ + ResourceType: k8sCluster, ValidationSchema: withWorkspaceValidationSchema, }) assert.NoError(t, err) err = schemaRepository.CreateReporterSchema(context.Background(), model.ReporterSchema{ - ResourceType: "k8s_cluster", - ReporterType: "ocm", + ResourceType: k8sCluster, + ReporterType: ocm, ValidationSchema: emptyValidationSchema, }) assert.NoError(t, err) err = schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ - ResourceType: "host", + ResourceType: host, ValidationSchema: withWorkspaceValidationSchema, }) assert.NoError(t, err) err = schemaRepository.CreateReporterSchema(context.Background(), model.ReporterSchema{ - ResourceType: "host", - ReporterType: "hbi", + ResourceType: host, + ReporterType: hbi, ValidationSchema: emptyValidationSchema, }) assert.NoError(t, err) @@ -1296,24 +1305,6 @@ func TestReportResource_ValidationErrors(t *testing.T) { cmd ReportResourceCommand expectError string }{ - { - name: "missing type", - cmd: func() ReportResourceCommand { - cmd := fixture(t).Basic("host", "hbi", "instance-1", "test-host", "ws-123") - cmd.ResourceType = "" - return cmd - }(), - expectError: "missing 'type' field", - }, - { - name: "missing reporterType", - cmd: func() ReportResourceCommand { - cmd := fixture(t).Basic("host", "hbi", "instance-1", "test-host", "ws-123") - cmd.ReporterType = "" - return cmd - }(), - expectError: "missing 'reporterType' field", - }, { name: "reporter type not allowed for resource type", cmd: fixture(t).Basic("host", "unknown_reporter", "instance-1", "test-host", "ws-123"), @@ -1484,15 +1475,20 @@ func TestReportResource_SchemaValidation(t *testing.T) { ctx := testAuthzContext() schemaRepository := data.NewInMemorySchemaRepository() - err := schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ - ResourceType: tc.resourceType, + rt, err := model.NewResourceType(tc.resourceType) + require.NoError(t, err) + rpt, err := model.NewReporterType(tc.reporterType) + require.NoError(t, err) + + err = schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ + ResourceType: rt, ValidationSchema: model.NewJsonSchemaValidatorFromString(tc.commonSchema), }) require.NoError(t, err) err = schemaRepository.CreateReporterSchema(context.Background(), model.ReporterSchema{ - ResourceType: tc.resourceType, - ReporterType: tc.reporterType, + ResourceType: rt, + ReporterType: rpt, ValidationSchema: model.NewJsonSchemaValidatorFromString(tc.reporterSchema), }) require.NoError(t, err) @@ -1565,24 +1561,6 @@ func TestReportResource_ValidationErrorFormat(t *testing.T) { cmd ReportResourceCommand expectErrorMsg string }{ - { - name: "missing type field", - cmd: func() ReportResourceCommand { - cmd := fixture(t).Basic("host", "hbi", "instance-1", "test-host", "ws-123") - cmd.ResourceType = "" - return cmd - }(), - expectErrorMsg: "failed validation for report resource: missing 'type' field", - }, - { - name: "missing reporterType field", - cmd: func() ReportResourceCommand { - cmd := fixture(t).Basic("host", "hbi", "instance-1", "test-host", "ws-123") - cmd.ReporterType = "" - return cmd - }(), - expectErrorMsg: "failed validation for report resource: missing 'reporterType' field", - }, { name: "unknown reporter for resource type", cmd: fixture(t).Basic("host", "unknown_reporter", "instance-1", "test-host", "ws-123"), diff --git a/internal/data/schema_inmemory.go b/internal/data/schema_inmemory.go index df2e13571..311d82ccf 100644 --- a/internal/data/schema_inmemory.go +++ b/internal/data/schema_inmemory.go @@ -26,10 +26,10 @@ type reporterEntry struct { bizmodel.ReporterSchema } -func (o *InMemorySchemaRepository) GetResourceSchemas(ctx context.Context) ([]string, error) { - var resourceTypes []string - for resourceType := range o.content { - resourceTypes = append(resourceTypes, resourceType) +func (o *InMemorySchemaRepository) GetResourceSchemas(ctx context.Context) ([]bizmodel.ResourceType, error) { + resourceTypes := make([]bizmodel.ResourceType, 0, len(o.content)) + for _, entry := range o.content { + resourceTypes = append(resourceTypes, entry.ResourceType) } return resourceTypes, nil @@ -38,21 +38,21 @@ func (o *InMemorySchemaRepository) GetResourceSchemas(ctx context.Context) ([]st func (o *InMemorySchemaRepository) CreateResourceSchema(ctx context.Context, resource bizmodel.ResourceSchema) error { resource.ResourceType = NormalizeResourceType(resource.ResourceType) - if _, ok := o.content[resource.ResourceType]; ok { + if _, ok := o.content[resource.ResourceType.String()]; ok { return fmt.Errorf("resource %s already exists", resource.ResourceType) } - o.content[resource.ResourceType] = &resourceEntry{ + o.content[resource.ResourceType.String()] = &resourceEntry{ ResourceSchema: resource, reporters: map[string]*reporterEntry{}, } return nil } -func (o *InMemorySchemaRepository) GetResourceSchema(ctx context.Context, resourceType string) (bizmodel.ResourceSchema, error) { +func (o *InMemorySchemaRepository) GetResourceSchema(ctx context.Context, resourceType bizmodel.ResourceType) (bizmodel.ResourceSchema, error) { resourceType = NormalizeResourceType(resourceType) - resource, ok := o.content[resourceType] + resource, ok := o.content[resourceType.String()] if !ok { return bizmodel.ResourceSchema{}, bizmodel.ResourceSchemaNotFound } @@ -63,12 +63,12 @@ func (o *InMemorySchemaRepository) GetResourceSchema(ctx context.Context, resour func (o *InMemorySchemaRepository) UpdateResourceSchema(ctx context.Context, resource bizmodel.ResourceSchema) error { resource.ResourceType = NormalizeResourceType(resource.ResourceType) - entry, ok := o.content[resource.ResourceType] + entry, ok := o.content[resource.ResourceType.String()] if !ok { return bizmodel.ResourceSchemaNotFound } - o.content[resource.ResourceType] = &resourceEntry{ + o.content[resource.ResourceType.String()] = &resourceEntry{ ResourceSchema: resource, reporters: entry.reporters, } @@ -76,18 +76,18 @@ func (o *InMemorySchemaRepository) UpdateResourceSchema(ctx context.Context, res return nil } -func (o *InMemorySchemaRepository) DeleteResourceSchema(ctx context.Context, resourceType string) error { +func (o *InMemorySchemaRepository) DeleteResourceSchema(ctx context.Context, resourceType bizmodel.ResourceType) error { resourceType = NormalizeResourceType(resourceType) - if _, ok := o.content[resourceType]; !ok { + if _, ok := o.content[resourceType.String()]; !ok { return bizmodel.ResourceSchemaNotFound } - delete(o.content, resourceType) + delete(o.content, resourceType.String()) return nil } -func (o *InMemorySchemaRepository) GetReporterSchemas(ctx context.Context, resourceType string) ([]string, error) { +func (o *InMemorySchemaRepository) GetReporterSchemas(ctx context.Context, resourceType bizmodel.ResourceType) ([]bizmodel.ReporterType, error) { resourceType = NormalizeResourceType(resourceType) entry, err := o.getResourceEntry(resourceType) @@ -95,7 +95,7 @@ func (o *InMemorySchemaRepository) GetReporterSchemas(ctx context.Context, resou return nil, err } - var reporters []string + reporters := make([]bizmodel.ReporterType, 0, len(entry.reporters)) for _, reporter := range entry.reporters { reporters = append(reporters, reporter.ReporterType) } @@ -105,34 +105,34 @@ func (o *InMemorySchemaRepository) GetReporterSchemas(ctx context.Context, resou func (o *InMemorySchemaRepository) CreateReporterSchema(ctx context.Context, resourceReporter bizmodel.ReporterSchema) error { resourceReporter.ResourceType = NormalizeResourceType(resourceReporter.ResourceType) - resourceReporter.ReporterType = normalizeReporterType(resourceReporter.ReporterType) + resourceReporter.ReporterType = NormalizeReporterType(resourceReporter.ReporterType) entry, err := o.getResourceEntry(resourceReporter.ResourceType) if err != nil { return err } - if _, ok := (entry.reporters)[resourceReporter.ReporterType]; ok { + if _, ok := entry.reporters[resourceReporter.ReporterType.String()]; ok { return fmt.Errorf("reporter %s for entry %s already exist", resourceReporter.ReporterType, resourceReporter.ResourceType) } - entry.reporters[resourceReporter.ReporterType] = &reporterEntry{ + entry.reporters[resourceReporter.ReporterType.String()] = &reporterEntry{ resourceReporter, } return nil } -func (o *InMemorySchemaRepository) GetReporterSchema(ctx context.Context, resourceType string, reporterType string) (bizmodel.ReporterSchema, error) { +func (o *InMemorySchemaRepository) GetReporterSchema(ctx context.Context, resourceType bizmodel.ResourceType, reporterType bizmodel.ReporterType) (bizmodel.ReporterSchema, error) { resourceType = NormalizeResourceType(resourceType) - reporterType = normalizeReporterType(reporterType) + reporterType = NormalizeReporterType(reporterType) entry, err := o.getResourceEntry(resourceType) if err != nil { return bizmodel.ReporterSchema{}, err } - reporter, ok := entry.reporters[reporterType] + reporter, ok := entry.reporters[reporterType.String()] if !ok { return bizmodel.ReporterSchema{}, bizmodel.ReporterSchemaNotFound } @@ -142,46 +142,46 @@ func (o *InMemorySchemaRepository) GetReporterSchema(ctx context.Context, resour func (o *InMemorySchemaRepository) UpdateReporterSchema(ctx context.Context, resourceReporter bizmodel.ReporterSchema) error { resourceReporter.ResourceType = NormalizeResourceType(resourceReporter.ResourceType) - resourceReporter.ReporterType = normalizeReporterType(resourceReporter.ReporterType) + resourceReporter.ReporterType = NormalizeReporterType(resourceReporter.ReporterType) entry, err := o.getResourceEntry(resourceReporter.ResourceType) if err != nil { return err } - if _, ok := entry.reporters[resourceReporter.ReporterType]; !ok { + if _, ok := entry.reporters[resourceReporter.ReporterType.String()]; !ok { return bizmodel.ReporterSchemaNotFound } - entry.reporters[resourceReporter.ReporterType] = &reporterEntry{ + entry.reporters[resourceReporter.ReporterType.String()] = &reporterEntry{ resourceReporter, } return nil } -func (o *InMemorySchemaRepository) DeleteReporterSchema(ctx context.Context, resourceType string, reporterType string) error { +func (o *InMemorySchemaRepository) DeleteReporterSchema(ctx context.Context, resourceType bizmodel.ResourceType, reporterType bizmodel.ReporterType) error { resourceType = NormalizeResourceType(resourceType) - reporterType = normalizeReporterType(reporterType) + reporterType = NormalizeReporterType(reporterType) entry, err := o.getResourceEntry(resourceType) if err != nil { return err } - if _, ok := entry.reporters[reporterType]; !ok { + if _, ok := entry.reporters[reporterType.String()]; !ok { return bizmodel.ReporterSchemaNotFound } - delete(entry.reporters, reporterType) + delete(entry.reporters, reporterType.String()) return nil } -func (o *InMemorySchemaRepository) getResourceEntry(resourceType string) (*resourceEntry, error) { +func (o *InMemorySchemaRepository) getResourceEntry(resourceType bizmodel.ResourceType) (*resourceEntry, error) { resourceType = NormalizeResourceType(resourceType) - if entry, ok := o.content[resourceType]; ok { + if entry, ok := o.content[resourceType.String()]; ok { return entry, nil } @@ -208,7 +208,11 @@ func NewInMemorySchemaRepositoryFromDir(ctx context.Context, resourceDir string, if !dir.IsDir() { continue } - resourceType := NormalizeResourceType(dir.Name()) + resourceType, err := bizmodel.NewResourceType(dir.Name()) + if err != nil { + return nil, fmt.Errorf("invalid resource type directory %q: %w", dir.Name(), err) + } + resourceType = NormalizeResourceType(resourceType) // Load and store common resource schema commonResourceSchema, err := loadCommonResourceDataSchema(resourceType, resourceDir) @@ -223,7 +227,7 @@ func NewInMemorySchemaRepositoryFromDir(ctx context.Context, resourceDir string, } } - reportersDir := filepath.Join(resourceDir, resourceType, "reporters") + reportersDir := filepath.Join(resourceDir, resourceType.String(), "reporters") if _, err := os.Stat(reportersDir); os.IsNotExist(err) { continue } @@ -238,7 +242,10 @@ func NewInMemorySchemaRepositoryFromDir(ctx context.Context, resourceDir string, if !reporter.IsDir() { continue } - reporterType := reporter.Name() + reporterType, err := bizmodel.NewReporterType(reporter.Name()) + if err != nil { + return nil, fmt.Errorf("invalid reporter type %q: %w", reporter.Name(), err) + } reporterSchema, isReporterSchemaExists, err := loadResourceSchema(resourceType, reporterType, resourceDir) if err == nil && isReporterSchemaExists { err = repository.CreateReporterSchema(ctx, bizmodel.ReporterSchema{ @@ -292,9 +299,13 @@ func NewFromJsonBytes(ctx context.Context, jsonBytes []byte, validationSchemaFro // Find the resources for key, value := range jsonContent { if strings.HasPrefix(key, commonPrefix) { - resourceType := key[len(commonPrefix):] - err := repository.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ - ResourceType: resourceType, + resourceTypeStr := key[len(commonPrefix):] + rt, err := bizmodel.NewResourceType(resourceTypeStr) + if err != nil { + return nil, fmt.Errorf("invalid resource type in schema JSON key %q: %w", key, err) + } + err = repository.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ + ResourceType: rt, ValidationSchema: validationSchemaFromString(value.(string)), }) @@ -311,14 +322,17 @@ func NewFromJsonBytes(ctx context.Context, jsonBytes []byte, validationSchemaFro // Find Reporters for key, value := range jsonContent { - resourceType := findResourceTypeFromJsonKey(key, resourceTypes) - if resourceType == "" { + rt, ok := findResourceTypeFromJsonKey(key, resourceTypes) + if !ok { continue } - reporterType := key[len(resourceType)+1:] - err := repository.CreateReporterSchema(ctx, bizmodel.ReporterSchema{ - ResourceType: resourceType, + reporterType, err := bizmodel.NewReporterType(key[len(rt.String())+1:]) + if err != nil { + return nil, fmt.Errorf("invalid reporter type in schema JSON key %q: %w", key, err) + } + err = repository.CreateReporterSchema(ctx, bizmodel.ReporterSchema{ + ResourceType: rt, ReporterType: reporterType, ValidationSchema: validationSchemaFromString(value.(string)), }) @@ -330,8 +344,8 @@ func NewFromJsonBytes(ctx context.Context, jsonBytes []byte, validationSchemaFro return &repository, nil } -func loadResourceSchema(resourceType string, reporterType string, dir string) (string, bool, error) { - schemaPath := filepath.Join(dir, resourceType, "reporters", reporterType, fmt.Sprintf("%s.json", resourceType)) +func loadResourceSchema(resourceType bizmodel.ResourceType, reporterType bizmodel.ReporterType, dir string) (string, bool, error) { + schemaPath := filepath.Join(dir, resourceType.String(), "reporters", reporterType.String(), fmt.Sprintf("%s.json", resourceType)) // Check if file exists if _, err := os.Stat(schemaPath); err != nil { @@ -350,9 +364,8 @@ func loadResourceSchema(resourceType string, reporterType string, dir string) (s return string(data), true, nil } -func loadCommonResourceDataSchema(resourceType string, baseSchemaDir string) (string, error) { - - schemaPath := filepath.Join(baseSchemaDir, resourceType, "common_representation.json") +func loadCommonResourceDataSchema(resourceType bizmodel.ResourceType, baseSchemaDir string) (string, error) { + schemaPath := filepath.Join(baseSchemaDir, resourceType.String(), "common_representation.json") data, err := os.ReadFile(schemaPath) if err != nil { @@ -361,20 +374,20 @@ func loadCommonResourceDataSchema(resourceType string, baseSchemaDir string) (st return string(data), nil } -func NormalizeResourceType(resourceType string) string { - return strings.ToLower(strings.ReplaceAll(resourceType, "/", "_")) +func NormalizeReporterType(reporterType bizmodel.ReporterType) bizmodel.ReporterType { + return bizmodel.DeserializeReporterType(strings.ToLower(reporterType.String())) } -func normalizeReporterType(reporterType string) string { - return strings.ToLower(reporterType) +func NormalizeResourceType(resourceType bizmodel.ResourceType) bizmodel.ResourceType { + return bizmodel.DeserializeResourceType(strings.ToLower(strings.ReplaceAll(resourceType.String(), "/", "_"))) } -func findResourceTypeFromJsonKey(jsonKey string, resourceTypes []string) string { - for _, resourceType := range resourceTypes { - if strings.HasPrefix(jsonKey, resourceType+":") { - return resourceType +func findResourceTypeFromJsonKey(jsonKey string, resourceTypes []bizmodel.ResourceType) (bizmodel.ResourceType, bool) { + for _, rt := range resourceTypes { + if strings.HasPrefix(jsonKey, rt.String()+":") { + return rt, true } } - return "" + return bizmodel.ResourceType(""), false } diff --git a/internal/data/schema_inmemory_test.go b/internal/data/schema_inmemory_test.go index 271d06ba7..94ff72897 100644 --- a/internal/data/schema_inmemory_test.go +++ b/internal/data/schema_inmemory_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" bizmodel "github.com/project-kessel/inventory-api/internal/biz/model" ) @@ -17,18 +18,20 @@ func TestInMemorySchemaRepository_CreateResource(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) // Verify resource was created - retrieved, err := repo.GetResourceSchema(ctx, "host") + retrieved, err := repo.GetResourceSchema(ctx, rt) assert.NoError(t, err) - assert.Equal(t, "host", retrieved.ResourceType) + assert.Equal(t, rt, retrieved.ResourceType) assert.Equal(t, validateSchemaTypeObject, retrieved.ValidationSchema) } @@ -36,12 +39,14 @@ func TestInMemorySchemaRepository_CreateResource_AlreadyExists(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) // Try to create the same resource again @@ -71,16 +76,23 @@ func TestInMemorySchemaRepository_GetResource(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() - err := repo.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ - ResourceType: tc.createType, + createType, err := bizmodel.NewResourceType(tc.createType) + require.NoError(t, err) + lookupType, err := bizmodel.NewResourceType(tc.lookupType) + require.NoError(t, err) + expectedType, err := bizmodel.NewResourceType(tc.expectedType) + require.NoError(t, err) + + err = repo.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ + ResourceType: createType, ValidationSchema: validateSchemaTypeObject, }) assert.NoError(t, err) - retrieved, err := repo.GetResourceSchema(ctx, tc.lookupType) + retrieved, err := repo.GetResourceSchema(ctx, lookupType) assert.NoError(t, err) assert.Equal(t, bizmodel.ResourceSchema{ - ResourceType: tc.expectedType, + ResourceType: expectedType, ValidationSchema: validateSchemaTypeObject, }, retrieved) }) @@ -91,7 +103,9 @@ func TestInMemorySchemaRepository_GetResource_NotFound(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() - _, err := repo.GetResourceSchema(ctx, "nonexistent") + rt, err := bizmodel.NewResourceType("nonexistent") + require.NoError(t, err) + _, err = repo.GetResourceSchema(ctx, rt) assert.ErrorIs(t, err, bizmodel.ResourceSchemaNotFound) } @@ -99,17 +113,19 @@ func TestInMemorySchemaRepository_UpdateResource(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) // Update the resource updatedResource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: bizmodel.NewJsonSchemaValidatorFromString(`{"type": "object", "properties": {"name": {"type": "string"}}}`), } @@ -117,7 +133,7 @@ func TestInMemorySchemaRepository_UpdateResource(t *testing.T) { assert.NoError(t, err) // Verify update - retrieved, err := repo.GetResourceSchema(ctx, "host") + retrieved, err := repo.GetResourceSchema(ctx, rt) assert.NoError(t, err) assert.Equal(t, updatedResource.ValidationSchema, retrieved.ValidationSchema) } @@ -126,12 +142,14 @@ func TestInMemorySchemaRepository_UpdateResource_NotFound(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("nonexistent") + require.NoError(t, err) resource := bizmodel.ResourceSchema{ - ResourceType: "nonexistent", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.UpdateResourceSchema(ctx, resource) + err = repo.UpdateResourceSchema(ctx, resource) assert.ErrorIs(t, err, bizmodel.ResourceSchemaNotFound) } @@ -139,19 +157,21 @@ func TestInMemorySchemaRepository_DeleteResource(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) - err = repo.DeleteResourceSchema(ctx, "host") + err = repo.DeleteResourceSchema(ctx, rt) assert.NoError(t, err) // Verify deletion - _, err = repo.GetResourceSchema(ctx, "host") + _, err = repo.GetResourceSchema(ctx, rt) assert.ErrorIs(t, err, bizmodel.ResourceSchemaNotFound) } @@ -159,10 +179,17 @@ func TestInMemorySchemaRepository_GetResources(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rtHost, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + rtK8sCluster, err := bizmodel.NewResourceType("k8s_cluster") + require.NoError(t, err) + rtK8sPolicy, err := bizmodel.NewResourceType("k8s_policy") + require.NoError(t, err) + resources := []bizmodel.ResourceSchema{ - {ResourceType: "host", ValidationSchema: validateSchemaTypeObject}, - {ResourceType: "k8s_cluster", ValidationSchema: validateSchemaTypeObject}, - {ResourceType: "k8s_policy", ValidationSchema: validateSchemaTypeObject}, + {ResourceType: rtHost, ValidationSchema: validateSchemaTypeObject}, + {ResourceType: rtK8sCluster, ValidationSchema: validateSchemaTypeObject}, + {ResourceType: rtK8sPolicy, ValidationSchema: validateSchemaTypeObject}, } for _, r := range resources { @@ -173,9 +200,9 @@ func TestInMemorySchemaRepository_GetResources(t *testing.T) { retrieved, err := repo.GetResourceSchemas(ctx) assert.NoError(t, err) assert.Len(t, retrieved, 3) - assert.Contains(t, retrieved, "host") - assert.Contains(t, retrieved, "k8s_cluster") - assert.Contains(t, retrieved, "k8s_policy") + assert.Contains(t, retrieved, rtHost) + assert.Contains(t, retrieved, rtK8sCluster) + assert.Contains(t, retrieved, rtK8sPolicy) } func TestInMemorySchemaRepository_CreateResourceReporter(t *testing.T) { @@ -198,24 +225,32 @@ func TestInMemorySchemaRepository_CreateResourceReporter(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() - err := repo.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ - ResourceType: "host", + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + err = repo.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, }) assert.NoError(t, err) + createRep, err := bizmodel.NewReporterType(tc.createReporterType) + require.NoError(t, err) err = repo.CreateReporterSchema(ctx, bizmodel.ReporterSchema{ - ResourceType: "host", - ReporterType: tc.createReporterType, + ResourceType: rt, + ReporterType: createRep, ValidationSchema: reporterValidation, }) assert.NoError(t, err) - retrieved, err := repo.GetReporterSchema(ctx, "host", tc.lookupReporterType) + lookupRep, err := bizmodel.NewReporterType(tc.lookupReporterType) + require.NoError(t, err) + retrieved, err := repo.GetReporterSchema(ctx, rt, lookupRep) assert.NoError(t, err) + expectedRep, err := bizmodel.NewReporterType(tc.expectedReporterType) + require.NoError(t, err) assert.Equal(t, bizmodel.ReporterSchema{ - ResourceType: "host", - ReporterType: tc.expectedReporterType, + ResourceType: rt, + ReporterType: expectedRep, ValidationSchema: reporterValidation, }, retrieved) }) @@ -226,16 +261,20 @@ func TestInMemorySchemaRepository_GetResourceReporter_NotFound(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) // Create resource first resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) + repNonexistent, err := bizmodel.NewReporterType("nonexistent") + require.NoError(t, err) // Try to get non-existent reporter - _, err = repo.GetReporterSchema(ctx, "host", "nonexistent") + _, err = repo.GetReporterSchema(ctx, rt, repNonexistent) assert.ErrorIs(t, err, bizmodel.ReporterSchemaNotFound) } @@ -243,17 +282,22 @@ func TestInMemorySchemaRepository_UpdateResourceReporter(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + // Create resource and reporter resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) reporter := bizmodel.ReporterSchema{ - ResourceType: "host", - ReporterType: "hbi", + ResourceType: rt, + ReporterType: repHbi, ValidationSchema: validateSchemaTypeObject, } err = repo.CreateReporterSchema(ctx, reporter) @@ -261,15 +305,15 @@ func TestInMemorySchemaRepository_UpdateResourceReporter(t *testing.T) { // Update reporter updatedReporter := bizmodel.ReporterSchema{ - ResourceType: "host", - ReporterType: "hbi", + ResourceType: rt, + ReporterType: repHbi, ValidationSchema: bizmodel.NewJsonSchemaValidatorFromString(`{"type": "object", "properties": {"satellite_id": {"type": "string"}}}`), } err = repo.UpdateReporterSchema(ctx, updatedReporter) assert.NoError(t, err) // Verify update - retrieved, err := repo.GetReporterSchema(ctx, "host", "hbi") + retrieved, err := repo.GetReporterSchema(ctx, rt, repHbi) assert.NoError(t, err) assert.Equal(t, updatedReporter.ValidationSchema, retrieved.ValidationSchema) } @@ -278,28 +322,33 @@ func TestInMemorySchemaRepository_DeleteResourceReporter(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + // Create resource and reporter resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) reporter := bizmodel.ReporterSchema{ - ResourceType: "host", - ReporterType: "hbi", + ResourceType: rt, + ReporterType: repHbi, ValidationSchema: validateSchemaTypeObject, } err = repo.CreateReporterSchema(ctx, reporter) assert.NoError(t, err) // Delete reporter - err = repo.DeleteReporterSchema(ctx, "host", "hbi") + err = repo.DeleteReporterSchema(ctx, rt, repHbi) assert.NoError(t, err) // Verify deletion - _, err = repo.GetReporterSchema(ctx, "host", "hbi") + _, err = repo.GetReporterSchema(ctx, rt, repHbi) assert.ErrorIs(t, err, bizmodel.ReporterSchemaNotFound) } @@ -307,19 +356,28 @@ func TestInMemorySchemaRepository_GetResourceReporters(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + repSatellite, err := bizmodel.NewReporterType("satellite") + require.NoError(t, err) + repInsights, err := bizmodel.NewReporterType("insights") + require.NoError(t, err) + // Create resource resource := bizmodel.ResourceSchema{ - ResourceType: "host", + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, } - err := repo.CreateResourceSchema(ctx, resource) + err = repo.CreateResourceSchema(ctx, resource) assert.NoError(t, err) // Create multiple reporters reporters := []bizmodel.ReporterSchema{ - {ResourceType: "host", ReporterType: "hbi", ValidationSchema: validateSchemaTypeObject}, - {ResourceType: "host", ReporterType: "satellite", ValidationSchema: validateSchemaTypeObject}, - {ResourceType: "host", ReporterType: "insights", ValidationSchema: validateSchemaTypeObject}, + {ResourceType: rt, ReporterType: repHbi, ValidationSchema: validateSchemaTypeObject}, + {ResourceType: rt, ReporterType: repSatellite, ValidationSchema: validateSchemaTypeObject}, + {ResourceType: rt, ReporterType: repInsights, ValidationSchema: validateSchemaTypeObject}, } for _, r := range reporters { @@ -328,12 +386,12 @@ func TestInMemorySchemaRepository_GetResourceReporters(t *testing.T) { } // Get all reporters for resource - retrieved, err := repo.GetReporterSchemas(ctx, "host") + retrieved, err := repo.GetReporterSchemas(ctx, rt) assert.NoError(t, err) assert.Len(t, retrieved, 3) - assert.Contains(t, retrieved, "hbi") - assert.Contains(t, retrieved, "satellite") - assert.Contains(t, retrieved, "insights") + assert.Contains(t, retrieved, repHbi) + assert.Contains(t, retrieved, repSatellite) + assert.Contains(t, retrieved, repInsights) } func TestNewFromDir_InvalidDirectory(t *testing.T) { @@ -370,17 +428,22 @@ func TestNewFromDir_ValidDirectory(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, repo) + rtHost, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + // Verify resource was loaded - resource, err := repo.GetResourceSchema(ctx, "host") + resource, err := repo.GetResourceSchema(ctx, rtHost) assert.NoError(t, err) - assert.Equal(t, "host", resource.ResourceType) + assert.Equal(t, rtHost, resource.ResourceType) assert.Equal(t, bizmodel.NewJsonSchemaValidatorFromString(commonSchema), resource.ValidationSchema) // Verify reporter was loaded - reporter, err := repo.GetReporterSchema(ctx, "host", "hbi") + reporter, err := repo.GetReporterSchema(ctx, rtHost, repHbi) assert.NoError(t, err) - assert.Equal(t, "host", reporter.ResourceType) - assert.Equal(t, "hbi", reporter.ReporterType) + assert.Equal(t, rtHost, reporter.ResourceType) + assert.Equal(t, repHbi, reporter.ReporterType) assert.Equal(t, bizmodel.NewJsonSchemaValidatorFromString(reporterSchema), reporter.ValidationSchema) } @@ -410,16 +473,21 @@ func TestNewFromJsonFile_ValidFile(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, repo) + rtHost, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + // Verify resource was loaded - resource, err := repo.GetResourceSchema(ctx, "host") + resource, err := repo.GetResourceSchema(ctx, rtHost) assert.NoError(t, err) - assert.Equal(t, "host", resource.ResourceType) + assert.Equal(t, rtHost, resource.ResourceType) // Verify reporter was loaded - reporter, err := repo.GetReporterSchema(ctx, "host", "hbi") + reporter, err := repo.GetReporterSchema(ctx, rtHost, repHbi) assert.NoError(t, err) - assert.Equal(t, "host", reporter.ResourceType) - assert.Equal(t, "hbi", reporter.ReporterType) + assert.Equal(t, rtHost, reporter.ResourceType) + assert.Equal(t, repHbi, reporter.ReporterType) } func TestNewFromJsonBytes_ValidJSON(t *testing.T) { @@ -436,22 +504,31 @@ func TestNewFromJsonBytes_ValidJSON(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, repo) + rtHost, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + rtK8sCluster, err := bizmodel.NewResourceType("k8s_cluster") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + repAcm, err := bizmodel.NewReporterType("acm") + require.NoError(t, err) + // Verify resources were loaded resources, err := repo.GetResourceSchemas(ctx) assert.NoError(t, err) - assert.Contains(t, resources, "host") - assert.Contains(t, resources, "k8s_cluster") + assert.Contains(t, resources, rtHost) + assert.Contains(t, resources, rtK8sCluster) // Verify reporters were loaded - hostReporter, err := repo.GetReporterSchema(ctx, "host", "hbi") + hostReporter, err := repo.GetReporterSchema(ctx, rtHost, repHbi) assert.NoError(t, err) - assert.Equal(t, "host", hostReporter.ResourceType) - assert.Equal(t, "hbi", hostReporter.ReporterType) + assert.Equal(t, rtHost, hostReporter.ResourceType) + assert.Equal(t, repHbi, hostReporter.ReporterType) - k8sReporter, err := repo.GetReporterSchema(ctx, "k8s_cluster", "acm") + k8sReporter, err := repo.GetReporterSchema(ctx, rtK8sCluster, repAcm) assert.NoError(t, err) - assert.Equal(t, "k8s_cluster", k8sReporter.ResourceType) - assert.Equal(t, "acm", k8sReporter.ReporterType) + assert.Equal(t, rtK8sCluster, k8sReporter.ResourceType) + assert.Equal(t, repAcm, k8sReporter.ReporterType) } func TestNewFromJsonBytes_InvalidJSON(t *testing.T) { @@ -477,15 +554,20 @@ func TestNewFromJsonBytes_OnlyCommonSchemas(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, repo) + rtHost, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + rtK8sCluster, err := bizmodel.NewResourceType("k8s_cluster") + require.NoError(t, err) + // Verify resources were loaded resources, err := repo.GetResourceSchemas(ctx) assert.NoError(t, err) assert.Len(t, resources, 2) - assert.Contains(t, resources, "host") - assert.Contains(t, resources, "k8s_cluster") + assert.Contains(t, resources, rtHost) + assert.Contains(t, resources, rtK8sCluster) // Verify no reporters exist - reporters, err := repo.GetReporterSchemas(ctx, "host") + reporters, err := repo.GetReporterSchemas(ctx, rtHost) assert.NoError(t, err) assert.Empty(t, reporters) } @@ -554,7 +636,12 @@ func TestLoadResourceSchema(t *testing.T) { err := tt.setupFiles(tmpDir) assert.NoError(t, err) - schemaContent, exists, err := loadResourceSchema(tt.resourceType, tt.reporterType, tmpDir) + rt, err := bizmodel.NewResourceType(tt.resourceType) + require.NoError(t, err) + rep, err := bizmodel.NewReporterType(tt.reporterType) + require.NoError(t, err) + + schemaContent, exists, err := loadResourceSchema(rt, rep, tmpDir) if tt.expectErr { assert.Error(t, err) @@ -622,7 +709,10 @@ func TestLoadCommonResourceDataSchema(t *testing.T) { err := tt.setupFiles(tmpDir) assert.NoError(t, err) - schemaContent, err := loadCommonResourceDataSchema(tt.resourceType, tmpDir) + rt, err := bizmodel.NewResourceType(tt.resourceType) + require.NoError(t, err) + + schemaContent, err := loadCommonResourceDataSchema(rt, tmpDir) if tt.expectErr { assert.Error(t, err) @@ -692,7 +782,15 @@ func TestNormalizeResourceType(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := NormalizeResourceType(tt.input) + var input bizmodel.ResourceType + if tt.input == "" { + input = bizmodel.ResourceType("") + } else { + var err error + input, err = bizmodel.NewResourceType(tt.input) + require.NoError(t, err) + } + result := NormalizeResourceType(input).String() assert.Equal(t, tt.expected, result) }) } @@ -714,9 +812,14 @@ func TestLoadResourceSchema_ComplexScenarios(t *testing.T) { assert.NoError(t, err) } + rtHost, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + // Verify each reporter has its own schema for _, reporter := range reporters { - schemaContent, exists, err := loadResourceSchema("host", reporter, tmpDir) + rep, err := bizmodel.NewReporterType(reporter) + require.NoError(t, err) + schemaContent, exists, err := loadResourceSchema(rtHost, rep, tmpDir) assert.NoError(t, err) assert.True(t, exists) assert.Contains(t, schemaContent, reporter+"_id") @@ -738,9 +841,14 @@ func TestLoadResourceSchema_ComplexScenarios(t *testing.T) { assert.NoError(t, err) } + repAcm, err := bizmodel.NewReporterType("acm") + require.NoError(t, err) + // Verify each resource has its own ACM schema for _, resource := range resources { - schemaContent, exists, err := loadResourceSchema(resource, "acm", tmpDir) + rt, err := bizmodel.NewResourceType(resource) + require.NoError(t, err) + schemaContent, exists, err := loadResourceSchema(rt, repAcm, tmpDir) assert.NoError(t, err) assert.True(t, exists) assert.Contains(t, schemaContent, resource+"_field") @@ -769,7 +877,9 @@ func TestLoadCommonResourceDataSchema_ComplexScenarios(t *testing.T) { // Verify each resource has its own common schema for resourceType, expectedSchema := range resources { - schemaContent, err := loadCommonResourceDataSchema(resourceType, tmpDir) + rt, err := bizmodel.NewResourceType(resourceType) + require.NoError(t, err) + schemaContent, err := loadCommonResourceDataSchema(rt, tmpDir) assert.NoError(t, err) assert.Equal(t, expectedSchema, schemaContent) } @@ -780,21 +890,26 @@ func TestReporterMutationsPersist(t *testing.T) { repo := NewInMemorySchemaRepository() ctx := context.Background() - err := repo.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ - ResourceType: "host", + rt, err := bizmodel.NewResourceType("host") + require.NoError(t, err) + repHbi, err := bizmodel.NewReporterType("hbi") + require.NoError(t, err) + + err = repo.CreateResourceSchema(ctx, bizmodel.ResourceSchema{ + ResourceType: rt, ValidationSchema: validateSchemaTypeObject, }) assert.NoError(t, err) err = repo.CreateReporterSchema(ctx, bizmodel.ReporterSchema{ - ResourceType: "host", - ReporterType: "hbi", + ResourceType: rt, + ReporterType: repHbi, ValidationSchema: validateSchemaTypeObject, }) assert.NoError(t, err) - reporters, _ := repo.GetReporterSchemas(ctx, "host") - assert.Contains(t, reporters, "hbi") + reporters, _ := repo.GetReporterSchemas(ctx, rt) + assert.Contains(t, reporters, repHbi) } diff --git a/internal/service/resources/kesselinventoryservice_test.go b/internal/service/resources/kesselinventoryservice_test.go index 96cb50eb5..324869862 100644 --- a/internal/service/resources/kesselinventoryservice_test.go +++ b/internal/service/resources/kesselinventoryservice_test.go @@ -477,7 +477,7 @@ func TestToLookupObjectsResponse(t *testing.T) { model.DeserializeLocalResourceId("abc123"), &rep, ), - "next-page-token", + model.DeserializeContinuationToken("next-page-token"), ) expected := &pb.StreamedListObjectsResponse{ @@ -3963,31 +3963,40 @@ func newFakeSchemaRepository(t *testing.T) model.SchemaRepository { } }`) - err := schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ - ResourceType: "k8s_cluster", + k8sCluster, err := model.NewResourceType("k8s_cluster") + require.NoError(t, err) + host, err := model.NewResourceType("host") + require.NoError(t, err) + ocm, err := model.NewReporterType("ocm") + require.NoError(t, err) + hbi, err := model.NewReporterType("hbi") + require.NoError(t, err) + + err = schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ + ResourceType: k8sCluster, ValidationSchema: withWorkspaceValidationSchema, }) - assert.NoError(t, err) + require.NoError(t, err) err = schemaRepository.CreateReporterSchema(context.Background(), model.ReporterSchema{ - ResourceType: "k8s_cluster", - ReporterType: "ocm", + ResourceType: k8sCluster, + ReporterType: ocm, ValidationSchema: emptyValidationSchema, }) - assert.NoError(t, err) + require.NoError(t, err) err = schemaRepository.CreateResourceSchema(context.Background(), model.ResourceSchema{ - ResourceType: "host", + ResourceType: host, ValidationSchema: withWorkspaceValidationSchema, }) - assert.NoError(t, err) + require.NoError(t, err) err = schemaRepository.CreateReporterSchema(context.Background(), model.ReporterSchema{ - ResourceType: "host", - ReporterType: "hbi", + ResourceType: host, + ReporterType: hbi, ValidationSchema: emptyValidationSchema, }) - assert.NoError(t, err) + require.NoError(t, err) return schemaRepository }