Refactor code for improved readability and maintainability; add utility functions for score calculation and archive processing
This commit is contained in:
+15
-21
@@ -273,6 +273,14 @@ func BuildMatchKeyFromExchange(exchange *CapturedExchangeV2) MatchKey {
|
||||
}
|
||||
}
|
||||
|
||||
// addTokenScore adds 10 points to score if token matches between two MatchKeys.
|
||||
func addTokenScore(score int, token1, token2 string) int {
|
||||
if token1 != "" && token1 == token2 {
|
||||
return score + 10
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// MatchScore returns how well two MatchKeys match (higher is better).
|
||||
// Returns -1 if operation names don't match.
|
||||
func (k MatchKey) MatchScore(other MatchKey) int {
|
||||
@@ -283,27 +291,13 @@ func (k MatchKey) MatchScore(other MatchKey) int {
|
||||
score := 1 // Base score for matching operation
|
||||
|
||||
// Bonus points for matching parameters
|
||||
if k.ProfileToken != "" && k.ProfileToken == other.ProfileToken {
|
||||
score += 10
|
||||
}
|
||||
if k.ConfigurationToken != "" && k.ConfigurationToken == other.ConfigurationToken {
|
||||
score += 10
|
||||
}
|
||||
if k.VideoSourceToken != "" && k.VideoSourceToken == other.VideoSourceToken {
|
||||
score += 10
|
||||
}
|
||||
if k.AudioSourceToken != "" && k.AudioSourceToken == other.AudioSourceToken {
|
||||
score += 10
|
||||
}
|
||||
if k.PresetToken != "" && k.PresetToken == other.PresetToken {
|
||||
score += 10
|
||||
}
|
||||
if k.NodeToken != "" && k.NodeToken == other.NodeToken {
|
||||
score += 10
|
||||
}
|
||||
if k.OSDToken != "" && k.OSDToken == other.OSDToken {
|
||||
score += 10
|
||||
}
|
||||
score = addTokenScore(score, k.ProfileToken, other.ProfileToken)
|
||||
score = addTokenScore(score, k.ConfigurationToken, other.ConfigurationToken)
|
||||
score = addTokenScore(score, k.VideoSourceToken, other.VideoSourceToken)
|
||||
score = addTokenScore(score, k.AudioSourceToken, other.AudioSourceToken)
|
||||
score = addTokenScore(score, k.PresetToken, other.PresetToken)
|
||||
score = addTokenScore(score, k.NodeToken, other.NodeToken)
|
||||
score = addTokenScore(score, k.OSDToken, other.OSDToken)
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
+8
-6
@@ -2,6 +2,7 @@
|
||||
package onviftesting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -40,7 +41,7 @@ type GoldenFileSet struct {
|
||||
// LoadGoldenManifest loads a manifest.json from a golden directory.
|
||||
func LoadGoldenManifest(goldenDir string) (*GoldenManifest, error) {
|
||||
manifestPath := filepath.Join(goldenDir, "manifest.json")
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
data, err := os.ReadFile(manifestPath) //nolint:gosec // Path is from test data directory, safe
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read manifest: %w", err)
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func LoadGoldenFiles(goldenDir string) (*GoldenFileSet, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
data, err := os.ReadFile(path) //nolint:gosec // Path is from filepath.Walk, safe
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %s: %w", path, err)
|
||||
}
|
||||
@@ -100,7 +101,7 @@ func LoadGoldenFiles(goldenDir string) (*GoldenFileSet, error) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to load golden files: %w", err)
|
||||
}
|
||||
|
||||
return set, nil
|
||||
@@ -171,6 +172,7 @@ func ValidateResponse(response interface{}, golden *GoldenFile) []string {
|
||||
actual, ok := responseData[field]
|
||||
if !ok {
|
||||
errors = append(errors, fmt.Sprintf("missing field: %s", field))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -192,12 +194,12 @@ func ValidateResponse(response interface{}, golden *GoldenFile) []string {
|
||||
func toMap(v interface{}) (map[string]interface{}, error) {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to marshal value: %w", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to unmarshal to map: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -230,7 +232,7 @@ func valuesEqual(expected, actual interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return string(e) == string(a)
|
||||
return bytes.Equal(e, a)
|
||||
}
|
||||
|
||||
// SaveGoldenFile saves a golden file to disk.
|
||||
|
||||
+59
-33
@@ -263,6 +263,58 @@ func NewMockSOAPServerV2(archivePath string) (*MockSOAPServerV2, error) {
|
||||
return mock, nil
|
||||
}
|
||||
|
||||
// processArchiveEntry processes a single tar archive entry (JSON file) and adds it to the capture.
|
||||
// Returns (isMetadata, error).
|
||||
func processArchiveEntry(header *tar.Header, data []byte, capture *CameraCaptureV2) (*CaptureMetadata, error) {
|
||||
// Check for metadata.json (V2 archives)
|
||||
if header.Name == "metadata.json" || strings.HasSuffix(header.Name, "/metadata.json") {
|
||||
var meta CaptureMetadata
|
||||
if err := json.Unmarshal(data, &meta); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal metadata: %w", err)
|
||||
}
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
// Skip files that look like request/response XML stored as JSON
|
||||
if strings.Contains(header.Name, "_request") || strings.Contains(header.Name, "_response") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Parse exchange from JSON
|
||||
exchange, err := parseExchange(header.Name, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exchange != nil {
|
||||
capture.Exchanges = append(capture.Exchanges, *exchange)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// parseExchange parses a JSON exchange entry, supporting both V1 and V2 formats.
|
||||
func parseExchange(fileName string, data []byte) (*CapturedExchangeV2, error) {
|
||||
version := DetectCaptureVersion(data)
|
||||
if version >= "2.0" {
|
||||
var exchange CapturedExchangeV2
|
||||
if err := json.Unmarshal(data, &exchange); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal V2 %s: %w", fileName, err)
|
||||
}
|
||||
return &exchange, nil
|
||||
}
|
||||
|
||||
// V1 format - convert to V2
|
||||
var v1Exchange CapturedExchange
|
||||
if err := json.Unmarshal(data, &v1Exchange); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal V1 %s: %w", fileName, err)
|
||||
}
|
||||
v2Exchange := ConvertV1ToV2(&v1Exchange)
|
||||
// Extract parameters from V1 request body
|
||||
v2Exchange.Parameters = ExtractParameters(v2Exchange.OperationName, v2Exchange.RequestBody)
|
||||
v2Exchange.ServiceType = DetermineServiceType(v2Exchange.RequestBody)
|
||||
return v2Exchange, nil
|
||||
}
|
||||
|
||||
// LoadCaptureFromArchiveV2 loads captures from archive, supporting both V1 and V2 formats.
|
||||
func LoadCaptureFromArchiveV2(archivePath string) (*CameraCaptureV2, *CaptureMetadata, error) {
|
||||
file, err := os.Open(archivePath) //nolint:gosec // File path is from test data, safe
|
||||
@@ -308,40 +360,13 @@ func LoadCaptureFromArchiveV2(archivePath string) (*CameraCaptureV2, *CaptureMet
|
||||
return nil, nil, fmt.Errorf("failed to read file %s: %w", header.Name, err)
|
||||
}
|
||||
|
||||
// Check for metadata.json (V2 archives)
|
||||
if header.Name == "metadata.json" || strings.HasSuffix(header.Name, "/metadata.json") {
|
||||
var meta CaptureMetadata
|
||||
if err := json.Unmarshal(data, &meta); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal metadata: %w", err)
|
||||
}
|
||||
metadata = &meta
|
||||
continue
|
||||
// Process the archive entry
|
||||
meta, err := processArchiveEntry(header, data, capture)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Skip files that look like request/response XML stored as JSON
|
||||
if strings.Contains(header.Name, "_request") || strings.Contains(header.Name, "_response") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Detect version and unmarshal accordingly
|
||||
version := DetectCaptureVersion(data)
|
||||
if version >= "2.0" {
|
||||
var exchange CapturedExchangeV2
|
||||
if err := json.Unmarshal(data, &exchange); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal V2 %s: %w", header.Name, err)
|
||||
}
|
||||
capture.Exchanges = append(capture.Exchanges, exchange)
|
||||
} else {
|
||||
// V1 format - convert to V2
|
||||
var v1Exchange CapturedExchange
|
||||
if err := json.Unmarshal(data, &v1Exchange); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal V1 %s: %w", header.Name, err)
|
||||
}
|
||||
v2Exchange := ConvertV1ToV2(&v1Exchange)
|
||||
// Extract parameters from V1 request body
|
||||
v2Exchange.Parameters = ExtractParameters(v2Exchange.OperationName, v2Exchange.RequestBody)
|
||||
v2Exchange.ServiceType = DetermineServiceType(v2Exchange.RequestBody)
|
||||
capture.Exchanges = append(capture.Exchanges, *v2Exchange)
|
||||
if meta != nil {
|
||||
metadata = meta
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,6 +521,7 @@ func ExtractParameters(operationName, soapBody string) map[string]interface{} {
|
||||
for i := 1; i < len(matches); i++ {
|
||||
if matches[i] != "" {
|
||||
params[paramName] = strings.TrimSpace(matches[i])
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,9 +417,10 @@ func ReadOperationsByService(service ServiceType) []OperationSpec {
|
||||
return EventReadOperations
|
||||
case ServiceDeviceIO:
|
||||
return DeviceIOReadOperations
|
||||
default:
|
||||
case ServiceUnknown:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IndependentOperations returns operations that don't depend on other operations.
|
||||
|
||||
+16
-9
@@ -9,6 +9,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const percentScale = 100
|
||||
|
||||
// Registry holds information about all available camera captures.
|
||||
type Registry struct {
|
||||
Version string `json:"version"`
|
||||
@@ -47,7 +49,7 @@ const DefaultRegistryPath = "testdata/captures/registry.json"
|
||||
|
||||
// LoadRegistry loads the capture registry from a file.
|
||||
func LoadRegistry(path string) (*Registry, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
data, err := os.ReadFile(path) //nolint:gosec // Registry path is from constant or test data, safe
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Return empty registry if file doesn't exist
|
||||
@@ -92,12 +94,13 @@ func SaveRegistry(registry *Registry, path string) error {
|
||||
}
|
||||
|
||||
// AddCamera adds a new camera to the registry.
|
||||
func (r *Registry) AddCamera(entry CameraEntry) {
|
||||
func (r *Registry) AddCamera(entry *CameraEntry) {
|
||||
// Check if camera already exists
|
||||
for i, cam := range r.Cameras {
|
||||
for i := range r.Cameras {
|
||||
cam := &r.Cameras[i]
|
||||
if cam.ID == entry.ID {
|
||||
// Update existing entry
|
||||
r.Cameras[i] = entry
|
||||
r.Cameras[i] = *entry
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -106,7 +109,7 @@ func (r *Registry) AddCamera(entry CameraEntry) {
|
||||
if entry.AddedDate == "" {
|
||||
entry.AddedDate = time.Now().Format("2006-01-02")
|
||||
}
|
||||
r.Cameras = append(r.Cameras, entry)
|
||||
r.Cameras = append(r.Cameras, *entry)
|
||||
}
|
||||
|
||||
// GetCamera retrieves a camera entry by ID.
|
||||
@@ -121,12 +124,14 @@ func (r *Registry) GetCamera(id string) *CameraEntry {
|
||||
|
||||
// RemoveCamera removes a camera from the registry.
|
||||
func (r *Registry) RemoveCamera(id string) bool {
|
||||
for i, cam := range r.Cameras {
|
||||
for i := range r.Cameras {
|
||||
cam := &r.Cameras[i]
|
||||
if cam.ID == id {
|
||||
r.Cameras = append(r.Cameras[:i], r.Cameras[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -164,7 +169,7 @@ func (r *Registry) UpdateCoverage() {
|
||||
}
|
||||
|
||||
// GetTotalCoverage returns the total coverage across all services.
|
||||
func (r *Registry) GetTotalCoverage() (total int, captured int) {
|
||||
func (r *Registry) GetTotalCoverage() (total, captured int) {
|
||||
for _, cov := range r.Coverage {
|
||||
total += cov.Total
|
||||
captured += cov.Captured
|
||||
@@ -238,6 +243,7 @@ func CreateCameraEntryFromCapture(archivePath string) (*CameraEntry, error) {
|
||||
cameraInfo.Manufacturer = ExtractXMLElement(ex.ResponseBody, "Manufacturer")
|
||||
cameraInfo.Model = ExtractXMLElement(ex.ResponseBody, "Model")
|
||||
cameraInfo.FirmwareVersion = ExtractXMLElement(ex.ResponseBody, "FirmwareVersion")
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -280,10 +286,11 @@ func detectCapabilities(capture *CameraCaptureV2) []string {
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
result := make([]string, 0, len(services))
|
||||
for svc := range services {
|
||||
result = append(result, svc)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -358,7 +365,7 @@ func (r *Registry) GetSummary() RegistrySummary {
|
||||
summary.TotalOperations += cov.Total
|
||||
summary.CapturedOperations += cov.Captured
|
||||
if cov.Total > 0 {
|
||||
summary.ServiceCoverage[service] = float64(cov.Captured) / float64(cov.Total) * 100
|
||||
summary.ServiceCoverage[service] = float64(cov.Captured) / float64(cov.Total) * percentScale
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user