Skip to content

Commit

Permalink
Add remaining accept_logic for collect service and JSON schema valida…
Browse files Browse the repository at this point in the history
…tion. Write tests.
  • Loading branch information
luisgmetzger committed Feb 12, 2025
1 parent 608a0f1 commit 30a9ae2
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 126 deletions.
68 changes: 46 additions & 22 deletions cmd/admin/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/json"
"log"
"net/http"
"os"
Expand All @@ -16,12 +17,18 @@ var ThisServiceName = "admin"

var ChQSHP = make(chan queueing.QSHP)

type DataField struct {
ID string `json:"id"`
Source string `json:"source"`
Payload string `json:"payload"`
}

type FetchRequestInput struct {
Scheme string `json:"scheme" maxLength:"10" doc:"Resource scheme"`
Host string `json:"host" maxLength:"500" doc:"Host of resource"`
Path string `json:"path" maxLength:"1500" doc:"Path to resource"`
APIKey string `json:"api-key"`
Data string `json:"data"`
Scheme string `json:"scheme" maxLength:"10" doc:"Resource scheme"`
Host string `json:"host" maxLength:"500" doc:"Host of resource"`
Path string `json:"path" maxLength:"1500" doc:"Path to resource"`
APIKey string `json:"api-key"`
Data DataField `json:"data"` // Nested struct for data
}

// https://dev.to/kashifsoofi/rest-api-with-go-chi-and-inmemory-store-43ag
Expand All @@ -33,16 +40,20 @@ func FetchRequestHandler(c *gin.Context) {

if fri.APIKey == os.Getenv("API_KEY") || true {
zap.L().Debug("fetch enqueue",
zap.String("scheme", fri.Scheme),
zap.String("host", fri.Host),
zap.String("path", fri.Path),
zap.String("path", fri.Data))
zap.String("data_id", fri.Data.ID),
zap.String("payload", fri.Data.Payload),
zap.String("source", fri.Data.Source))

rawData, _ := json.Marshal(fri)
ChQSHP <- queueing.QSHP{
Queue: "collect",
Scheme: fri.Scheme,
Host: fri.Host,
Path: fri.Path,
RawData: fri.Data,
RawData: string(rawData),
}

ChQSHP <- queueing.QSHP{
Expand All @@ -64,35 +75,48 @@ func EntreeRequestHandler(c *gin.Context) {
if err := c.ShouldBindJSON(&fri); err != nil {
zap.L().Error("failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON: " + err.Error()})

return
}

full := c.Param("fullorone")
hallPass := c.Param("hallpass")

if fri.APIKey == os.Getenv("API_KEY") || true {
hallPassB := false
fullB := false

if hallPass == "pass" {
hallPassB = true
}

if full == "full" {
fullB = true
hallPassB := hallPass == "pass"
fullB := full == "full"

// Create enriched rawData including hallPass and full flags
rawDataMap := map[string]interface{}{
"scheme": fri.Scheme,
"host": fri.Host,
"path": fri.Path,
"api-key": fri.APIKey,
"data": fri.Data,
"fullCrawl": fullB,
"hallpass": hallPassB,
}
rawData, _ := json.Marshal(rawDataMap)

zap.L().Debug("entree enqueue",
zap.String("scheme", fri.Scheme),
zap.String("host", fri.Host),
zap.String("path", fri.Path),
zap.String("data", fri.Data))
zap.String("data_id", fri.Data.ID),
zap.String("payload", fri.Data.Payload),
zap.String("source", fri.Data.Source),
zap.Bool("fullCrawl", fullB),
zap.Bool("hallpass", hallPassB))

// Enqueue "collect" job
ChQSHP <- queueing.QSHP{
Queue: "collect",
Scheme: fri.Scheme,
Host: fri.Host,
Path: fri.Path,
RawData: fri.Data,
Queue: "collect",
Scheme: fri.Scheme,
Host: fri.Host,
Path: fri.Path,
IsFull: fullB,
IsHallPass: hallPassB,
RawData: string(rawData), // Embedded enriched RawData
}

ChQSHP <- queueing.QSHP{
Expand Down
39 changes: 9 additions & 30 deletions cmd/collect/accept_logic.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"encoding/json"
"errors"
"fmt"

Expand All @@ -23,60 +22,40 @@ func InitializeSchemas() error {
}

var err error
entreeSchema, err = common.LoadJSONSchema("cmd/collect/schemas/entree_schema.json")
// Load schemas through embedded filesystem
entreeSchema, err = common.LoadEmbeddedSchema("entree_schema.json")
if err != nil {
return fmt.Errorf("failed to load entree schema: %w", err)
}

fetchSchema, err = common.LoadJSONSchema("cmd/collect/schemas/fetch_schema.json")
fetchSchema, err = common.LoadEmbeddedSchema("fetch_schema.json")
if err != nil {
return fmt.Errorf("failed to load fetch schema: %w", err)
}

// Mark schemas as initialized
initialized = true

return nil
}

// ValidateJSON validates a JSON object against a schema.
func ValidateJSON(schema *gojsonschema.Schema, rawJSON string) error {
zap.L().Info("Validating JSON", zap.String("json", rawJSON))
documentLoader := gojsonschema.NewStringLoader(rawJSON)

result, err := schema.Validate(documentLoader)
if err != nil {
return fmt.Errorf("schema validation error: %w", err)
}

if !result.Valid() {
for _, desc := range result.Errors() {
zap.L().Error("JSON validation error", zap.String("field", desc.Field()), zap.String("description", desc.Description()))
zap.L().Error("JSON validation error", zap.String("field", desc.Field()), zap.String("description", desc.Description())) //nolint:lll
}
return errors.New("JSON validation failed")
}
return nil
}

func HandleBusinessLogic(args common.CollectArgs, jsonString string) error {
zap.L().Info("Handling business logic", zap.String("json", jsonString))

// Parse JSON into a map
var jsonData map[string]interface{}
if err := json.Unmarshal([]byte(jsonString), &jsonData); err != nil {
zap.L().Error("JSON unmarshaling failed", zap.Error(err))
return err
}

// Extract and validate `source` field from JSON
source, ok := jsonData["source"].(string)
if !ok || source == "" {
return errors.New("missing or invalid `source` field in JSON")
return errors.New("JSON validation failed")
}

// Log validation success
zap.L().Info("JSON successfully validated and processed",
zap.String("source", source))

// Business logic based on `source`
zap.L().Info("Processing source", zap.String("source", source))
// Add source-specific processing logic here...

return nil
}
82 changes: 53 additions & 29 deletions cmd/collect/accept_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,74 @@ package main
import (
"testing"

"github.com/GSA-TTS/jemison/internal/common"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)

func TestTransformArgumentsToJSON(t *testing.T) {
// Sample input
args := common.CollectArgs{
Scheme: "https",
Host: "www.example.com",
Path: "/test",
}
func TestValidateJSON(t *testing.T) {
// Set up valid and invalid JSON examples
validJSON := `{"scheme":"https","host":"example.gov","path":"/api/resource","api-key":"key123","data":{"id":"unique-fetch-id-5678","source":"fetch","payload":"some payload"}}` //nolint:lll
invalidJSON := `{"scheme":"https","host":"example.gov","path":"/api/resource","data":{"id":"123"}}`

// Fetch schema should be already initialized for this test
err := InitializeSchemas()
assert.NoError(t, err, "Schema initialization should not fail")

// Test valid JSON
err = ValidateJSON(fetchSchema, validJSON)
assert.NoError(t, err, "Valid JSON should pass validation")

jsonString, err := TransformArgumentsToJSON(args)
// Test invalid JSON
err = ValidateJSON(fetchSchema, invalidJSON)
assert.Error(t, err, "Invalid JSON should fail validation")
}

func TestInitializeSchemas(t *testing.T) {
// First initialization
err := InitializeSchemas()
assert.NoError(t, err, "First schema initialization should succeed")

// Validate
assert.NoError(t, err, "TransformArgumentsToJSON should not return an error")
assert.JSONEq(t, `{"Scheme":"https","Host":"www.example.com","Path":"/test"}`, jsonString, "JSON output is incorrect")
// Subsequent initialization call (idempotency check)
err = InitializeSchemas()
assert.NoError(t, err, "Subsequent schema initialization should succeed")
}

func TestHandleBusinessLogic(t *testing.T) {
func TestSelectSchema(t *testing.T) {
// Mock zap logger
loggerConfig := zap.NewDevelopmentConfig()
loggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)

logger, _ := loggerConfig.Build()
zap.ReplaceGlobals(logger)

defer func() {
if err := logger.Sync(); err != nil {
t.Logf("failed to sync logger: %v", err)
}
}()
// Valid entree JSON
entreeData := map[string]interface{}{
"data": map[string]interface{}{
"source": "entree",
},
}

zap.ReplaceGlobals(logger)
// Valid fetch JSON
fetchData := map[string]interface{}{
"data": map[string]interface{}{
"source": "fetch",
},
}

// Sample input
args := common.CollectArgs{
Scheme: "http",
Host: "example.org",
Path: "/example",
// Invalid schema JSON
invalidData := map[string]interface{}{
"data": map[string]interface{}{
"source": "unknown",
},
}

err := HandleBusinessLogic(args)
// Check schema selection
schema, err := selectSchema(entreeData)
assert.NoError(t, err)
assert.Equal(t, entreeSchema, schema)

schema, err = selectSchema(fetchData)
assert.NoError(t, err)
assert.Equal(t, fetchSchema, schema)

// Validate
assert.NoError(t, err, "HandleBusinessLogic should not return an error")
_, err = selectSchema(invalidData)
assert.Error(t, err, "Unknown source should result in error")
}
3 changes: 3 additions & 0 deletions cmd/collect/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func main() {

// Create database connection
JDB = postgres.NewJemisonDB()

fmt.Println(ThisServiceName, " environment initialized")

// Setting up HTTP engine
Expand All @@ -62,6 +63,8 @@ func main() {
zap.L().Info("listening from collect", zap.String("port", env.Env.Port))

// Start the service
//
//nolint:gosec // G114: Ignoring timeout settings for demonstration purposes or due to intentional design
if err := http.ListenAndServe(":"+env.Env.Port, engine); err != nil {
zap.Error(err)
}
Expand Down
Loading

0 comments on commit 30a9ae2

Please sign in to comment.