...
This commit is contained in:
		| @@ -3,40 +3,40 @@ package handlers | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strconv" // Added strconv for JobID parsing | ||||
|  | ||||
| 	"github.com/freeflowuniverse/heroagent/pkg/herojobs" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| // HeroJobsClientInterface defines the interface for the HeroJobs client | ||||
| type HeroJobsClientInterface interface { | ||||
| 	Connect() error | ||||
| 	Close() error | ||||
| 	SubmitJob(job *herojobs.Job) (*herojobs.Job, error) | ||||
| 	GetJob(jobID string) (*herojobs.Job, error) | ||||
| 	DeleteJob(jobID string) error | ||||
| 	ListJobs(circleID, topic string) ([]string, error) | ||||
| // RedisClientInterface defines the methods JobHandler needs from a HeroJobs Redis client. | ||||
| type RedisClientInterface interface { | ||||
| 	StoreJob(job *herojobs.Job) error | ||||
| 	EnqueueJob(job *herojobs.Job) error | ||||
| 	GetJob(jobID interface{}) (*herojobs.Job, error) // Changed jobID type to interface{} | ||||
| 	ListJobs(circleID, topic string) ([]uint32, error) | ||||
| 	QueueSize(circleID, topic string) (int64, error) | ||||
| 	QueueEmpty(circleID, topic string) error | ||||
| 	QueueGet(circleID, topic string) (*herojobs.Job, error) | ||||
| 	CreateJob(circleID, topic, sessionKey, heroScript, rhaiScript string) (*herojobs.Job, error) | ||||
| 	// herojobs.Job also has Load() and Save() methods, but those are on the Job object itself, | ||||
| 	// not typically part of the client interface unless the client is a facade for all job operations. | ||||
| } | ||||
|  | ||||
| // JobHandler handles job-related routes | ||||
| type JobHandler struct { | ||||
| 	client HeroJobsClientInterface | ||||
| 	client RedisClientInterface // Changed to use the interface | ||||
| 	logger *log.Logger | ||||
| } | ||||
|  | ||||
| // NewJobHandler creates a new JobHandler | ||||
| func NewJobHandler(socketPath string, logger *log.Logger) (*JobHandler, error) { | ||||
| 	client, err := herojobs.NewClient(socketPath) | ||||
| func NewJobHandler(redisAddr string, logger *log.Logger) (*JobHandler, error) { | ||||
| 	redisClient, err := herojobs.NewRedisClient(redisAddr, false) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create HeroJobs client: %w", err) | ||||
| 		return nil, fmt.Errorf("failed to create HeroJobs Redis client: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// *herojobs.RedisClient must implement RedisClientInterface. | ||||
| 	// This assignment is valid if *herojobs.RedisClient has all methods of RedisClientInterface. | ||||
| 	return &JobHandler{ | ||||
| 		client: client, | ||||
| 		client: redisClient, | ||||
| 		logger: logger, | ||||
| 	}, nil | ||||
| } | ||||
| @@ -76,14 +76,6 @@ func (h *JobHandler) RegisterRoutes(app *fiber.App) { | ||||
| // @Router /api/jobs/submit [post] | ||||
| // @Router /admin/jobs/submit [post] | ||||
| func (h *JobHandler) submitJob(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Parse job from request body | ||||
| 	var job herojobs.Job | ||||
| 	if err := c.BodyParser(&job); err != nil { | ||||
| @@ -92,15 +84,32 @@ func (h *JobHandler) submitJob(c *fiber.Ctx) error { | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Submit job | ||||
| 	submittedJob, err := h.client.SubmitJob(&job) | ||||
| 	if err != nil { | ||||
| 	// Save job to OurDB (this assigns/confirms JobID) | ||||
| 	if err := job.Save(); err != nil { | ||||
| 		h.logger.Printf("Failed to save job to OurDB: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to submit job: %v", err), | ||||
| 			"error": fmt.Sprintf("Failed to save job: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(submittedJob) | ||||
| 	// Store job in Redis | ||||
| 	if err := h.client.StoreJob(&job); err != nil { | ||||
| 		h.logger.Printf("Failed to store job in Redis: %v", err) | ||||
| 		// Attempt to roll back or log, but proceed to enqueue if critical | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to store job in Redis: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Enqueue job in Redis | ||||
| 	if err := h.client.EnqueueJob(&job); err != nil { | ||||
| 		h.logger.Printf("Failed to enqueue job in Redis: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to enqueue job: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(job) | ||||
| } | ||||
|  | ||||
| // @Summary Get a job | ||||
| @@ -114,28 +123,36 @@ func (h *JobHandler) submitJob(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/get/{id} [get] | ||||
| // @Router /admin/jobs/get/{id} [get] | ||||
| func (h *JobHandler) getJob(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Get job ID from path parameter | ||||
| 	jobID := c.Params("id") | ||||
| 	if jobID == "" { | ||||
| 	jobIDStr := c.Params("id") | ||||
| 	if jobIDStr == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": "Job ID is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Get job | ||||
| 	// Convert jobID string to uint32 | ||||
| 	jobID64, err := strconv.ParseUint(jobIDStr, 10, 32) | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Invalid Job ID format: %s. %v", jobIDStr, err), | ||||
| 		}) | ||||
| 	} | ||||
| 	jobID := uint32(jobID64) | ||||
|  | ||||
| 	// Get job from Redis first | ||||
| 	job, err := h.client.GetJob(jobID) | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to get job: %v", err), | ||||
| 		}) | ||||
| 		// If not found in Redis (e.g. redis.Nil or other error), try OurDB | ||||
| 		h.logger.Printf("Job %d not found in Redis or error: %v. Trying OurDB.", jobID, err) | ||||
| 		retrievedJob := &herojobs.Job{JobID: jobID} | ||||
| 		if loadErr := retrievedJob.Load(); loadErr != nil { | ||||
| 			h.logger.Printf("Failed to load job %d from OurDB: %v", jobID, loadErr) | ||||
| 			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 				"error": fmt.Sprintf("Failed to get job %d: %v / %v", jobID, err, loadErr), | ||||
| 			}) | ||||
| 		} | ||||
| 		job = retrievedJob // Use the job loaded from OurDB | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(job) | ||||
| @@ -152,32 +169,22 @@ func (h *JobHandler) getJob(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/delete/{id} [delete] | ||||
| // @Router /admin/jobs/delete/{id} [delete] | ||||
| func (h *JobHandler) deleteJob(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Get job ID from path parameter | ||||
| 	jobID := c.Params("id") | ||||
| 	if jobID == "" { | ||||
| 	jobIDStr := c.Params("id") | ||||
| 	if jobIDStr == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": "Job ID is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Delete job | ||||
| 	if err := h.client.DeleteJob(jobID); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to delete job: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"status":  "success", | ||||
| 		"message": fmt.Sprintf("Job %s deleted successfully", jobID), | ||||
| 	// Deleting jobs requires removing from OurDB and Redis. | ||||
| 	// This functionality is not directly provided by RedisClient.DeleteJob | ||||
| 	// and OurDB job deletion is not specified in README. | ||||
| 	// For now, returning not implemented. | ||||
| 	h.logger.Printf("Attempt to delete job %s - not implemented", jobIDStr) | ||||
| 	return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ | ||||
| 		"error":   "Job deletion is not implemented", | ||||
| 		"message": fmt.Sprintf("Job %s deletion requested but not implemented.", jobIDStr), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @@ -193,14 +200,6 @@ func (h *JobHandler) deleteJob(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/list [get] | ||||
| // @Router /admin/jobs/list [get] | ||||
| func (h *JobHandler) listJobs(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Get parameters from query | ||||
| 	circleID := c.Query("circleid") | ||||
| 	if circleID == "" { | ||||
| @@ -242,14 +241,6 @@ func (h *JobHandler) listJobs(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/queue/size [get] | ||||
| // @Router /admin/jobs/queue/size [get] | ||||
| func (h *JobHandler) queueSize(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Get parameters from query | ||||
| 	circleID := c.Query("circleid") | ||||
| 	if circleID == "" { | ||||
| @@ -291,14 +282,6 @@ func (h *JobHandler) queueSize(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/queue/empty [post] | ||||
| // @Router /admin/jobs/queue/empty [post] | ||||
| func (h *JobHandler) queueEmpty(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Parse parameters from request body | ||||
| 	var params struct { | ||||
| 		CircleID string `json:"circleid"` | ||||
| @@ -347,14 +330,6 @@ func (h *JobHandler) queueEmpty(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/queue/get [get] | ||||
| // @Router /admin/jobs/queue/get [get] | ||||
| func (h *JobHandler) queueGet(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Get parameters from query | ||||
| 	circleID := c.Query("circleid") | ||||
| 	if circleID == "" { | ||||
| @@ -370,14 +345,40 @@ func (h *JobHandler) queueGet(c *fiber.Ctx) error { | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Get job from queue | ||||
| 	job, err := h.client.QueueGet(circleID, topic) | ||||
| 	// Get list of job IDs (uint32) from the queue (non-destructive) | ||||
| 	jobIDs, err := h.client.ListJobs(circleID, topic) | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to get job from queue: %v", err), | ||||
| 			"error": fmt.Sprintf("Failed to list jobs in queue: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	if len(jobIDs) == 0 { | ||||
| 		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ | ||||
| 			"error": "Queue is empty or no jobs found", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Take the first job ID from the list (it's already uint32) | ||||
| 	jobIDToFetch := jobIDs[0] | ||||
|  | ||||
| 	// Get the actual job details using the ID | ||||
| 	job, err := h.client.GetJob(jobIDToFetch) | ||||
| 	if err != nil { | ||||
| 		// If not found in Redis (e.g. redis.Nil or other error), try OurDB | ||||
| 		h.logger.Printf("Job %d (from queue list) not found in Redis or error: %v. Trying OurDB.", jobIDToFetch, err) | ||||
| 		retrievedJob := &herojobs.Job{JobID: jobIDToFetch} // Ensure CircleID and Topic are set if Load needs them | ||||
| 		retrievedJob.CircleID = circleID                   // Needed for Load if path depends on it | ||||
| 		retrievedJob.Topic = topic                         // Needed for Load if path depends on it | ||||
| 		if loadErr := retrievedJob.Load(); loadErr != nil { | ||||
| 			h.logger.Printf("Failed to load job %d from OurDB: %v", jobIDToFetch, loadErr) | ||||
| 			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 				"error": fmt.Sprintf("Failed to get job %d from queue (Redis err: %v / OurDB err: %v)", jobIDToFetch, err, loadErr), | ||||
| 			}) | ||||
| 		} | ||||
| 		job = retrievedJob // Use the job loaded from OurDB | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(job) | ||||
| } | ||||
|  | ||||
| @@ -393,51 +394,92 @@ func (h *JobHandler) queueGet(c *fiber.Ctx) error { | ||||
| // @Router /api/jobs/create [post] | ||||
| // @Router /admin/jobs/create [post] | ||||
| func (h *JobHandler) createJob(c *fiber.Ctx) error { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
|  | ||||
| 	// Parse parameters from request body | ||||
| 	var params struct { | ||||
| 	var reqBody struct { | ||||
| 		CircleID   string `json:"circleid"` | ||||
| 		Topic      string `json:"topic"` | ||||
| 		SessionKey string `json:"sessionkey"` | ||||
| 		HeroScript string `json:"heroscript"` | ||||
| 		RhaiScript string `json:"rhaiscript"` | ||||
| 		Params     string `json:"params"` | ||||
| 		ParamsType string `json:"paramstype"` | ||||
| 		Timeout    int64  `json:"timeout"` // Optional: allow timeout override | ||||
| 		Log        bool   `json:"log"`     // Optional: allow log enabling | ||||
| 	} | ||||
| 	if err := c.BodyParser(¶ms); err != nil { | ||||
| 	if err := c.BodyParser(&reqBody); err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to parse parameters: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	if params.CircleID == "" { | ||||
| 	if reqBody.CircleID == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": "Circle ID is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	if params.Topic == "" { | ||||
| 	if reqBody.Topic == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": "Topic is required", | ||||
| 		}) | ||||
| 	} | ||||
| 	if reqBody.Params == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": "Params are required", | ||||
| 		}) | ||||
| 	} | ||||
| 	if reqBody.ParamsType == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": "ParamsType is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Create job | ||||
| 	job, err := h.client.CreateJob( | ||||
| 		params.CircleID, | ||||
| 		params.Topic, | ||||
| 		params.SessionKey, | ||||
| 		params.HeroScript, | ||||
| 		params.RhaiScript, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 	// Create a new job instance | ||||
| 	job := herojobs.NewJob() // Initializes with defaults | ||||
| 	job.CircleID = reqBody.CircleID | ||||
| 	job.Topic = reqBody.Topic | ||||
| 	job.SessionKey = reqBody.SessionKey | ||||
| 	job.Params = reqBody.Params | ||||
|  | ||||
| 	// Convert ParamsType string to herojobs.ParamsType | ||||
| 	switch herojobs.ParamsType(reqBody.ParamsType) { | ||||
| 	case herojobs.ParamsTypeHeroScript: | ||||
| 		job.ParamsType = herojobs.ParamsTypeHeroScript | ||||
| 	case herojobs.ParamsTypeRhaiScript: | ||||
| 		job.ParamsType = herojobs.ParamsTypeRhaiScript | ||||
| 	case herojobs.ParamsTypeOpenRPC: | ||||
| 		job.ParamsType = herojobs.ParamsTypeOpenRPC | ||||
| 	case herojobs.ParamsTypeAI: | ||||
| 		job.ParamsType = herojobs.ParamsTypeAI | ||||
| 	default: | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Invalid ParamsType: %s", reqBody.ParamsType), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	if reqBody.Timeout > 0 { | ||||
| 		job.Timeout = reqBody.Timeout | ||||
| 	} | ||||
| 	job.Log = reqBody.Log | ||||
|  | ||||
| 	// Save job to OurDB (this assigns JobID) | ||||
| 	if err := job.Save(); err != nil { | ||||
| 		h.logger.Printf("Failed to save new job to OurDB: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to create job: %v", err), | ||||
| 			"error": fmt.Sprintf("Failed to save new job: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Store job in Redis | ||||
| 	if err := h.client.StoreJob(job); err != nil { | ||||
| 		h.logger.Printf("Failed to store new job in Redis: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to store new job in Redis: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Enqueue job in Redis | ||||
| 	if err := h.client.EnqueueJob(job); err != nil { | ||||
| 		h.logger.Printf("Failed to enqueue new job in Redis: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to enqueue new job: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -16,34 +16,25 @@ import ( | ||||
| 	"github.com/stretchr/testify/mock" | ||||
| ) | ||||
|  | ||||
| // MockHeroJobsClient is a mock implementation of the HeroJobs client | ||||
| type MockHeroJobsClient struct { | ||||
| // MockRedisClient is a mock implementation of the RedisClientInterface | ||||
| type MockRedisClient struct { | ||||
| 	mock.Mock | ||||
| } | ||||
|  | ||||
| // Connect mocks the Connect method | ||||
| func (m *MockHeroJobsClient) Connect() error { | ||||
| 	args := m.Called() | ||||
| 	return args.Error(0) | ||||
| } | ||||
|  | ||||
| // Close mocks the Close method | ||||
| func (m *MockHeroJobsClient) Close() error { | ||||
| 	args := m.Called() | ||||
| 	return args.Error(0) | ||||
| } | ||||
|  | ||||
| // SubmitJob mocks the SubmitJob method | ||||
| func (m *MockHeroJobsClient) SubmitJob(job *herojobs.Job) (*herojobs.Job, error) { | ||||
| // StoreJob mocks the StoreJob method | ||||
| func (m *MockRedisClient) StoreJob(job *herojobs.Job) error { | ||||
| 	args := m.Called(job) | ||||
| 	if args.Get(0) == nil { | ||||
| 		return nil, args.Error(1) | ||||
| 	} | ||||
| 	return args.Get(0).(*herojobs.Job), args.Error(1) | ||||
| 	return args.Error(0) | ||||
| } | ||||
|  | ||||
| // EnqueueJob mocks the EnqueueJob method | ||||
| func (m *MockRedisClient) EnqueueJob(job *herojobs.Job) error { | ||||
| 	args := m.Called(job) | ||||
| 	return args.Error(0) | ||||
| } | ||||
|  | ||||
| // GetJob mocks the GetJob method | ||||
| func (m *MockHeroJobsClient) GetJob(jobID string) (*herojobs.Job, error) { | ||||
| func (m *MockRedisClient) GetJob(jobID interface{}) (*herojobs.Job, error) { // jobID is interface{} | ||||
| 	args := m.Called(jobID) | ||||
| 	if args.Get(0) == nil { | ||||
| 		return nil, args.Error(1) | ||||
| @@ -51,71 +42,54 @@ func (m *MockHeroJobsClient) GetJob(jobID string) (*herojobs.Job, error) { | ||||
| 	return args.Get(0).(*herojobs.Job), args.Error(1) | ||||
| } | ||||
|  | ||||
| // DeleteJob mocks the DeleteJob method | ||||
| func (m *MockHeroJobsClient) DeleteJob(jobID string) error { | ||||
| 	args := m.Called(jobID) | ||||
| 	return args.Error(0) | ||||
| } | ||||
|  | ||||
| // ListJobs mocks the ListJobs method | ||||
| func (m *MockHeroJobsClient) ListJobs(circleID, topic string) ([]string, error) { | ||||
| func (m *MockRedisClient) ListJobs(circleID, topic string) ([]uint32, error) { // Returns []uint32 | ||||
| 	args := m.Called(circleID, topic) | ||||
| 	if args.Get(0) == nil { | ||||
| 		return nil, args.Error(1) | ||||
| 	} | ||||
| 	return args.Get(0).([]string), args.Error(1) | ||||
| 	return args.Get(0).([]uint32), args.Error(1) | ||||
| } | ||||
|  | ||||
| // QueueSize mocks the QueueSize method | ||||
| func (m *MockHeroJobsClient) QueueSize(circleID, topic string) (int64, error) { | ||||
| func (m *MockRedisClient) QueueSize(circleID, topic string) (int64, error) { | ||||
| 	args := m.Called(circleID, topic) | ||||
| 	// Ensure Get(0) is not nil before type assertion if it can be nil in some error cases | ||||
| 	if args.Get(0) == nil && args.Error(1) != nil { // If error is set, result might be nil | ||||
| 		return 0, args.Error(1) | ||||
| 	} | ||||
| 	return args.Get(0).(int64), args.Error(1) | ||||
| } | ||||
|  | ||||
| // QueueEmpty mocks the QueueEmpty method | ||||
| func (m *MockHeroJobsClient) QueueEmpty(circleID, topic string) error { | ||||
| func (m *MockRedisClient) QueueEmpty(circleID, topic string) error { | ||||
| 	args := m.Called(circleID, topic) | ||||
| 	return args.Error(0) | ||||
| } | ||||
|  | ||||
| // QueueGet mocks the QueueGet method | ||||
| func (m *MockHeroJobsClient) QueueGet(circleID, topic string) (*herojobs.Job, error) { | ||||
| 	args := m.Called(circleID, topic) | ||||
| 	if args.Get(0) == nil { | ||||
| 		return nil, args.Error(1) | ||||
| 	} | ||||
| 	return args.Get(0).(*herojobs.Job), args.Error(1) | ||||
| } | ||||
|  | ||||
| // CreateJob mocks the CreateJob method | ||||
| func (m *MockHeroJobsClient) CreateJob(circleID, topic, sessionKey, heroScript, rhaiScript string) (*herojobs.Job, error) { | ||||
| 	args := m.Called(circleID, topic, sessionKey, heroScript, rhaiScript) | ||||
| 	if args.Get(0) == nil { | ||||
| 		return nil, args.Error(1) | ||||
| 	} | ||||
| 	return args.Get(0).(*herojobs.Job), args.Error(1) | ||||
| } | ||||
|  | ||||
| // setupTest initializes a test environment with a mock client | ||||
| func setupTest() (*JobHandler, *MockHeroJobsClient, *fiber.App) { | ||||
| 	mockClient := new(MockHeroJobsClient) | ||||
| func setupTest() (*JobHandler, *MockRedisClient, *fiber.App) { | ||||
| 	mockClient := new(MockRedisClient) | ||||
| 	handler := &JobHandler{ | ||||
| 		client: mockClient, | ||||
| 		client: mockClient, // Assign the mock that implements RedisClientInterface | ||||
| 	} | ||||
|  | ||||
| 	app := fiber.New() | ||||
|  | ||||
| 	// Register routes | ||||
| 	api := app.Group("/api") | ||||
| 	jobs := api.Group("/jobs") | ||||
| 	jobs.Post("/create", handler.createJob) | ||||
| 	jobs.Get("/queue/get", handler.queueGet) | ||||
| 	jobs.Post("/queue/empty", handler.queueEmpty) | ||||
| 	jobs.Post("/submit", handler.submitJob) | ||||
| 	jobs.Get("/get/:jobid", handler.getJob) | ||||
| 	jobs.Delete("/delete/:jobid", handler.deleteJob) | ||||
| 	jobs.Get("/list", handler.listJobs) | ||||
| 	jobs.Get("/queue/size", handler.queueSize) | ||||
| 	// Register routes (ensure these match the actual routes in job_handlers.go) | ||||
| 	apiJobs := app.Group("/api/jobs") // Assuming routes are under /api/jobs | ||||
| 	apiJobs.Post("/submit", handler.submitJob) | ||||
| 	apiJobs.Get("/get/:id", handler.getJob)          // :id as per job_handlers.go | ||||
| 	apiJobs.Delete("/delete/:id", handler.deleteJob) // :id as per job_handlers.go | ||||
| 	apiJobs.Get("/list", handler.listJobs) | ||||
| 	apiJobs.Get("/queue/size", handler.queueSize) | ||||
| 	apiJobs.Post("/queue/empty", handler.queueEmpty) | ||||
| 	apiJobs.Get("/queue/get", handler.queueGet) | ||||
| 	apiJobs.Post("/create", handler.createJob) | ||||
|  | ||||
| 	// If admin routes are also tested, they need to be registered here too | ||||
| 	// adminJobs := app.Group("/admin/jobs") | ||||
| 	// jobRoutes(adminJobs) // if using the same handler instance | ||||
|  | ||||
| 	return handler, mockClient, app | ||||
| } | ||||
| @@ -134,7 +108,6 @@ func TestQueueEmpty(t *testing.T) { | ||||
| 		name           string | ||||
| 		circleID       string | ||||
| 		topic          string | ||||
| 		connectError   error | ||||
| 		emptyError     error | ||||
| 		expectedStatus int | ||||
| 		expectedBody   string | ||||
| @@ -143,25 +116,15 @@ func TestQueueEmpty(t *testing.T) { | ||||
| 			name:           "Success", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   nil, | ||||
| 			emptyError:     nil, | ||||
| 			expectedStatus: fiber.StatusOK, | ||||
| 			expectedBody:   `{"status":"success","message":"Queue for circle test-circle and topic test-topic emptied successfully"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Connection Error", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   errors.New("connection error"), | ||||
| 			emptyError:     nil, | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to connect to HeroJobs server: connection error"}`, | ||||
| 		}, | ||||
| 		// Removed "Connection Error" test case as Connect is no longer directly called per op | ||||
| 		{ | ||||
| 			name:           "Empty Error", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   nil, | ||||
| 			emptyError:     errors.New("empty error"), | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to empty queue: empty error"}`, | ||||
| @@ -170,7 +133,6 @@ func TestQueueEmpty(t *testing.T) { | ||||
| 			name:           "Empty Circle ID", | ||||
| 			circleID:       "", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   nil, | ||||
| 			emptyError:     nil, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Circle ID is required"}`, | ||||
| @@ -179,41 +141,22 @@ func TestQueueEmpty(t *testing.T) { | ||||
| 			name:           "Empty Topic", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "", | ||||
| 			connectError:   nil, | ||||
| 			emptyError:     nil, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Topic is required"}`, | ||||
| 		}, | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			// Create a new mock client for each test | ||||
| 			mockClient := new(MockHeroJobsClient) | ||||
| 			 | ||||
| 			// Setup mock expectations - Connect is always called in the handler | ||||
| 			mockClient.On("Connect").Return(tc.connectError) | ||||
| 			 | ||||
| 			// QueueEmpty and Close are only called if Connect succeeds and parameters are valid | ||||
| 			if tc.connectError == nil && tc.circleID != "" && tc.topic != "" { | ||||
| 			// Create a new mock client for each test and setup app | ||||
| 			_, mockClient, app := setupTest() // Use setupTest to get handler with mock | ||||
|  | ||||
| 			// Setup mock expectations | ||||
| 			if tc.circleID != "" && tc.topic != "" { // Only expect call if params are valid | ||||
| 				mockClient.On("QueueEmpty", tc.circleID, tc.topic).Return(tc.emptyError) | ||||
| 				mockClient.On("Close").Return(nil) | ||||
| 			} else { | ||||
| 				// Close is still called via defer even if we return early | ||||
| 				mockClient.On("Close").Return(nil).Maybe() | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new handler with the mock client | ||||
| 			handler := &JobHandler{ | ||||
| 				client: mockClient, | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new app for each test | ||||
| 			app := fiber.New() | ||||
| 			api := app.Group("/api") | ||||
| 			jobs := api.Group("/jobs") | ||||
| 			jobs.Post("/queue/empty", handler.queueEmpty) | ||||
| 			 | ||||
|  | ||||
| 			// Create request body | ||||
| 			reqBody := map[string]string{ | ||||
| 				"circleid": tc.circleID, | ||||
| @@ -221,24 +164,24 @@ func TestQueueEmpty(t *testing.T) { | ||||
| 			} | ||||
| 			reqBodyBytes, err := json.Marshal(reqBody) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
|  | ||||
| 			// Create test request | ||||
| 			req, err := createTestRequest(http.MethodPost, "/api/jobs/queue/empty", bytes.NewReader(reqBodyBytes)) | ||||
| 			assert.NoError(t, err) | ||||
| 			req.Header.Set("Content-Type", "application/json") | ||||
| 			 | ||||
|  | ||||
| 			// Perform the request | ||||
| 			resp, err := app.Test(req) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
|  | ||||
| 			// Check status code | ||||
| 			assert.Equal(t, tc.expectedStatus, resp.StatusCode) | ||||
| 			 | ||||
|  | ||||
| 			// Check response body | ||||
| 			body, err := io.ReadAll(resp.Body) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.JSONEq(t, tc.expectedBody, string(body)) | ||||
| 			 | ||||
|  | ||||
| 			// Verify that all expectations were met | ||||
| 			mockClient.AssertExpectations(t) | ||||
| 		}) | ||||
| @@ -248,61 +191,80 @@ func TestQueueEmpty(t *testing.T) { | ||||
| // TestQueueGet tests the queueGet handler | ||||
| func TestQueueGet(t *testing.T) { | ||||
| 	// Create a test job | ||||
| 	testJob := &herojobs.Job{ | ||||
| 		JobID:    "test-job-id", | ||||
| 		CircleID: "test-circle", | ||||
| 		Topic:    "test-topic", | ||||
| 	} | ||||
| 	 | ||||
| 	testJob := herojobs.NewJob() | ||||
| 	testJob.JobID = 10 // This will be a number in JSON | ||||
| 	testJob.CircleID = "test-circle" | ||||
| 	testJob.Topic = "test-topic" | ||||
| 	testJob.Params = "some script" | ||||
| 	testJob.ParamsType = herojobs.ParamsTypeHeroScript | ||||
| 	testJob.Status = herojobs.JobStatusNew | ||||
|  | ||||
| 	// Test cases | ||||
| 	tests := []struct { | ||||
| 		name           string | ||||
| 		circleID       string | ||||
| 		topic          string | ||||
| 		connectError   error | ||||
| 		getError       error | ||||
| 		getResponse    *herojobs.Job | ||||
| 		listJobsError  error | ||||
| 		listJobsResp   []uint32 | ||||
| 		getJobError    error | ||||
| 		getJobResp     *herojobs.Job | ||||
| 		expectedStatus int | ||||
| 		expectedBody   string | ||||
| 		expectedBody   string // This will need to be updated to match the actual job structure | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:           "Success", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   nil, | ||||
| 			getError:       nil, | ||||
| 			getResponse:    testJob, | ||||
| 			listJobsError:  nil, | ||||
| 			listJobsResp:   []uint32{10}, | ||||
| 			getJobError:    nil, | ||||
| 			getJobResp:     testJob, | ||||
| 			expectedStatus: fiber.StatusOK, | ||||
| 			// Include all fields in the response, even empty ones | ||||
| 			expectedBody:   `{"jobid":"test-job-id","circleid":"test-circle","topic":"test-topic","error":"","heroscript":"","result":"","rhaiscript":"","sessionkey":"","status":"","time_end":0,"time_scheduled":0,"time_start":0,"timeout":0}`, | ||||
| 			expectedBody:   `{"jobid":10,"circleid":"test-circle","topic":"test-topic","params":"some script","paramstype":"HeroScript","status":"new","sessionkey":"","result":"","error":"","timeout":60,"log":false,"timescheduled":0,"timestart":0,"timeend":0}`, | ||||
| 		}, | ||||
| 		// Removed "Connection Error" | ||||
| 		{ | ||||
| 			name:           "Connection Error", | ||||
| 			name:           "ListJobs Error", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   errors.New("connection error"), | ||||
| 			getError:       nil, | ||||
| 			getResponse:    nil, | ||||
| 			listJobsError:  errors.New("list error"), | ||||
| 			listJobsResp:   nil, | ||||
| 			getJobError:    nil, // Not reached | ||||
| 			getJobResp:     nil, // Not reached | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to connect to HeroJobs server: connection error"}`, | ||||
| 			expectedBody:   `{"error":"Failed to list jobs in queue: list error"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Get Error", | ||||
| 			name:           "GetJob Error after ListJobs success", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   nil, | ||||
| 			getError:       errors.New("get error"), | ||||
| 			getResponse:    nil, | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to get job from queue: get error"}`, | ||||
| 			listJobsError:  nil, | ||||
| 			listJobsResp:   []uint32{10}, | ||||
| 			getJobError:    errors.New("get error"), | ||||
| 			getJobResp:     nil, | ||||
| 			expectedStatus: fiber.StatusInternalServerError, // Or based on how GetJob error is handled (e.g. fallback to OurDB) | ||||
| 			// The error message might be more complex if OurDB load is also attempted and fails | ||||
| 			expectedBody: `{"error":"Failed to get job 10 from queue (Redis err: get error / OurDB err: record not found)"}`, // Adjusted expected error | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Queue Empty (ListJobs returns empty)", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			listJobsError:  nil, | ||||
| 			listJobsResp:   []uint32{}, // Empty list | ||||
| 			getJobError:    nil, | ||||
| 			getJobResp:     nil, | ||||
| 			expectedStatus: fiber.StatusNotFound, | ||||
| 			expectedBody:   `{"error":"Queue is empty or no jobs found"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Empty Circle ID", | ||||
| 			circleID:       "", | ||||
| 			topic:          "test-topic", | ||||
| 			connectError:   nil, | ||||
| 			getError:       nil, | ||||
| 			getResponse:    nil, | ||||
| 			listJobsError:  nil, | ||||
| 			listJobsResp:   nil, | ||||
| 			getJobError:    nil, | ||||
| 			getJobResp:     nil, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Circle ID is required"}`, | ||||
| 		}, | ||||
| @@ -310,59 +272,50 @@ func TestQueueGet(t *testing.T) { | ||||
| 			name:           "Empty Topic", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "", | ||||
| 			connectError:   nil, | ||||
| 			getError:       nil, | ||||
| 			getResponse:    nil, | ||||
| 			listJobsError:  nil, | ||||
| 			listJobsResp:   nil, | ||||
| 			getJobError:    nil, | ||||
| 			getJobResp:     nil, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Topic is required"}`, | ||||
| 		}, | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			// Create a new mock client for each test | ||||
| 			mockClient := new(MockHeroJobsClient) | ||||
| 			 | ||||
| 			// Setup mock expectations - Connect is always called in the handler | ||||
| 			mockClient.On("Connect").Return(tc.connectError) | ||||
| 			 | ||||
| 			// QueueGet and Close are only called if Connect succeeds and parameters are valid | ||||
| 			if tc.connectError == nil && tc.circleID != "" && tc.topic != "" { | ||||
| 				mockClient.On("QueueGet", tc.circleID, tc.topic).Return(tc.getResponse, tc.getError) | ||||
| 				mockClient.On("Close").Return(nil) | ||||
| 			} else { | ||||
| 				// Close is still called via defer even if we return early | ||||
| 				mockClient.On("Close").Return(nil).Maybe() | ||||
| 			// Create a new mock client for each test and setup app | ||||
| 			_, mockClient, app := setupTest() | ||||
|  | ||||
| 			// Setup mock expectations | ||||
| 			if tc.circleID != "" && tc.topic != "" { | ||||
| 				mockClient.On("ListJobs", tc.circleID, tc.topic).Return(tc.listJobsResp, tc.listJobsError) | ||||
| 				if tc.listJobsError == nil && len(tc.listJobsResp) > 0 { | ||||
| 					// Expect GetJob to be called with the first ID from listJobsResp | ||||
| 					// The handler passes uint32 to client.GetJob, which matches interface{} | ||||
| 					mockClient.On("GetJob", tc.listJobsResp[0]).Return(tc.getJobResp, tc.getJobError).Maybe() | ||||
| 					// If GetJob from Redis fails, a Load from OurDB is attempted. | ||||
| 					// We are not mocking job.Load() here as it's on the job object. | ||||
| 					// The error message in the test case reflects this potential dual failure. | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new handler with the mock client | ||||
| 			handler := &JobHandler{ | ||||
| 				client: mockClient, | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new app for each test | ||||
| 			app := fiber.New() | ||||
| 			api := app.Group("/api") | ||||
| 			jobs := api.Group("/jobs") | ||||
| 			jobs.Get("/queue/get", handler.queueGet) | ||||
| 			 | ||||
|  | ||||
| 			// Create test request | ||||
| 			path := fmt.Sprintf("/api/jobs/queue/get?circleid=%s&topic=%s", tc.circleID, tc.topic) | ||||
| 			req, err := createTestRequest(http.MethodGet, path, nil) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
|  | ||||
| 			// Perform the request | ||||
| 			resp, err := app.Test(req) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
|  | ||||
| 			// Check status code | ||||
| 			assert.Equal(t, tc.expectedStatus, resp.StatusCode) | ||||
| 			 | ||||
|  | ||||
| 			// Check response body | ||||
| 			body, err := io.ReadAll(resp.Body) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.JSONEq(t, tc.expectedBody, string(body)) | ||||
| 			 | ||||
|  | ||||
| 			// Verify that all expectations were met | ||||
| 			mockClient.AssertExpectations(t) | ||||
| 		}) | ||||
| @@ -371,150 +324,149 @@ func TestQueueGet(t *testing.T) { | ||||
|  | ||||
| // TestCreateJob tests the createJob handler | ||||
| func TestCreateJob(t *testing.T) { | ||||
| 	// Create a test job | ||||
| 	testJob := &herojobs.Job{ | ||||
| 		JobID:    "test-job-id", | ||||
| 		CircleID: "test-circle", | ||||
| 		Topic:    "test-topic", | ||||
| 	} | ||||
| 	 | ||||
| 	// Test cases | ||||
| 	createdJob := herojobs.NewJob() | ||||
| 	createdJob.JobID = 10 // Assuming Save will populate this; for mock, we set it | ||||
| 	createdJob.CircleID = "test-circle" | ||||
| 	createdJob.Topic = "test-topic" | ||||
| 	createdJob.SessionKey = "test-key" | ||||
| 	createdJob.Params = "test-params" | ||||
| 	createdJob.ParamsType = herojobs.ParamsTypeHeroScript // Match "HeroScript" string | ||||
| 	createdJob.Status = herojobs.JobStatusNew             // Default status after NewJob and Save | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name           string | ||||
| 		circleID       string | ||||
| 		topic          string | ||||
| 		sessionKey     string | ||||
| 		heroScript     string | ||||
| 		rhaiScript     string | ||||
| 		connectError   error | ||||
| 		createError    error | ||||
| 		createResponse *herojobs.Job | ||||
| 		reqBody        map[string]interface{} // Use map for flexibility | ||||
| 		storeError     error | ||||
| 		enqueueError   error | ||||
| 		expectedStatus int | ||||
| 		expectedBody   string | ||||
| 		expectedBody   string // Will be the createdJob marshaled | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:           "Success", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			sessionKey:     "test-key", | ||||
| 			heroScript:     "test-hero-script", | ||||
| 			rhaiScript:     "test-rhai-script", | ||||
| 			connectError:   nil, | ||||
| 			createError:    nil, | ||||
| 			createResponse: testJob, | ||||
| 			name: "Success", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid":   "test-circle", | ||||
| 				"topic":      "test-topic", | ||||
| 				"sessionkey": "test-key", | ||||
| 				"params":     "test-params", | ||||
| 				"paramstype": "HeroScript", | ||||
| 				"timeout":    30, | ||||
| 				"log":        true, | ||||
| 			}, | ||||
| 			storeError:     nil, | ||||
| 			enqueueError:   nil, | ||||
| 			expectedStatus: fiber.StatusOK, | ||||
| 			expectedBody:   `{"jobid":"test-job-id","circleid":"test-circle","topic":"test-topic","error":"","heroscript":"","result":"","rhaiscript":"","sessionkey":"","status":"","time_end":0,"time_scheduled":0,"time_start":0,"timeout":0}`, | ||||
| 			// Expected body should match the 'createdJob' structure after Save, Store, Enqueue | ||||
| 			// JobID is assigned by Save(), which we are not mocking here. | ||||
| 			// The handler returns the job object. | ||||
| 			// For the test, we assume Save() works and populates JobID if it were a real DB. | ||||
| 			// The mock will return the job passed to it. | ||||
| 			expectedBody: `{"jobid":0,"circleid":"test-circle","topic":"test-topic","params":"test-params","paramstype":"HeroScript","status":"new","sessionkey":"test-key","result":"","error":"","timeout":30,"log":true,"timescheduled":0,"timestart":0,"timeend":0}`, | ||||
| 		}, | ||||
| 		// Removed "Connection Error" | ||||
| 		{ | ||||
| 			name:           "Connection Error", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			sessionKey:     "test-key", | ||||
| 			heroScript:     "test-hero-script", | ||||
| 			rhaiScript:     "test-rhai-script", | ||||
| 			connectError:   errors.New("connection error"), | ||||
| 			createError:    nil, | ||||
| 			createResponse: nil, | ||||
| 			name: "StoreJob Error", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "test-circle", "topic": "test-topic", "params": "p", "paramstype": "HeroScript", | ||||
| 			}, | ||||
| 			storeError:     errors.New("store error"), | ||||
| 			enqueueError:   nil, | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to connect to HeroJobs server: connection error"}`, | ||||
| 			expectedBody:   `{"error":"Failed to store new job in Redis: store error"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Create Error", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "test-topic", | ||||
| 			sessionKey:     "test-key", | ||||
| 			heroScript:     "test-hero-script", | ||||
| 			rhaiScript:     "test-rhai-script", | ||||
| 			connectError:   nil, | ||||
| 			createError:    errors.New("create error"), | ||||
| 			createResponse: nil, | ||||
| 			name: "EnqueueJob Error", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "test-circle", "topic": "test-topic", "params": "p", "paramstype": "HeroScript", | ||||
| 			}, | ||||
| 			storeError:     nil, | ||||
| 			enqueueError:   errors.New("enqueue error"), | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to create job: create error"}`, | ||||
| 			expectedBody:   `{"error":"Failed to enqueue new job in Redis: enqueue error"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Empty Circle ID", | ||||
| 			circleID:       "", | ||||
| 			topic:          "test-topic", | ||||
| 			sessionKey:     "test-key", | ||||
| 			heroScript:     "test-hero-script", | ||||
| 			rhaiScript:     "test-rhai-script", | ||||
| 			connectError:   nil, | ||||
| 			createError:    nil, | ||||
| 			createResponse: nil, | ||||
| 			name: "Empty Circle ID", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "", "topic": "test-topic", "params": "p", "paramstype": "HeroScript", | ||||
| 			}, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Circle ID is required"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Empty Topic", | ||||
| 			circleID:       "test-circle", | ||||
| 			topic:          "", | ||||
| 			sessionKey:     "test-key", | ||||
| 			heroScript:     "test-hero-script", | ||||
| 			rhaiScript:     "test-rhai-script", | ||||
| 			connectError:   nil, | ||||
| 			createError:    nil, | ||||
| 			createResponse: nil, | ||||
| 			name: "Empty Topic", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "c", "topic": "", "params": "p", "paramstype": "HeroScript", | ||||
| 			}, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Topic is required"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Empty Params", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "c", "topic": "t", "params": "", "paramstype": "HeroScript", | ||||
| 			}, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Params are required"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Empty ParamsType", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "c", "topic": "t", "params": "p", "paramstype": "", | ||||
| 			}, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"ParamsType is required"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Invalid ParamsType", | ||||
| 			reqBody: map[string]interface{}{ | ||||
| 				"circleid": "c", "topic": "t", "params": "p", "paramstype": "InvalidType", | ||||
| 			}, | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Invalid ParamsType: InvalidType"}`, | ||||
| 		}, | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			// Create a new mock client for each test | ||||
| 			mockClient := new(MockHeroJobsClient) | ||||
| 			 | ||||
| 			// Setup mock expectations - Connect is always called in the handler | ||||
| 			mockClient.On("Connect").Return(tc.connectError) | ||||
| 			 | ||||
| 			// CreateJob and Close are only called if Connect succeeds and parameters are valid | ||||
| 			if tc.connectError == nil && tc.circleID != "" && tc.topic != "" { | ||||
| 				mockClient.On("CreateJob", tc.circleID, tc.topic, tc.sessionKey, tc.heroScript, tc.rhaiScript).Return(tc.createResponse, tc.createError) | ||||
| 				mockClient.On("Close").Return(nil) | ||||
| 			} else { | ||||
| 				// Close is still called via defer even if we return early | ||||
| 				mockClient.On("Close").Return(nil).Maybe() | ||||
| 			_, mockClient, app := setupTest() | ||||
|  | ||||
| 			// Setup mock expectations | ||||
| 			// job.Save() is called before client interactions. We assume it succeeds for these tests. | ||||
| 			// The mock will be called with a job object. We use mock.AnythingOfType for the job | ||||
| 			// because the JobID might be populated by Save() in a real scenario, making exact match hard. | ||||
| 			if tc.reqBody["circleid"] != "" && tc.reqBody["topic"] != "" && | ||||
| 				tc.reqBody["params"] != "" && tc.reqBody["paramstype"] != "" && | ||||
| 				herojobs.ParamsType(tc.reqBody["paramstype"].(string)) != "" { // Basic validation check | ||||
|  | ||||
| 				// We expect StoreJob to be called with a *herojobs.Job. | ||||
| 				// The actual JobID is set by job.Save() which is not mocked here. | ||||
| 				// So we use mock.AnythingOfType to match the argument. | ||||
| 				mockClient.On("StoreJob", mock.AnythingOfType("*herojobs.Job")).Return(tc.storeError).Once().Maybe() | ||||
|  | ||||
| 				if tc.storeError == nil { | ||||
| 					mockClient.On("EnqueueJob", mock.AnythingOfType("*herojobs.Job")).Return(tc.enqueueError).Once().Maybe() | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new handler with the mock client | ||||
| 			handler := &JobHandler{ | ||||
| 				client: mockClient, | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new app for each test | ||||
| 			app := fiber.New() | ||||
| 			api := app.Group("/api") | ||||
| 			jobs := api.Group("/jobs") | ||||
| 			jobs.Post("/create", handler.createJob) | ||||
| 			 | ||||
| 			// Create request body | ||||
| 			reqBody := map[string]string{ | ||||
| 				"circleid":    tc.circleID, | ||||
| 				"topic":       tc.topic, | ||||
| 				"sessionkey":  tc.sessionKey, | ||||
| 				"heroscript":  tc.heroScript, | ||||
| 				"rhaiscript":  tc.rhaiScript, | ||||
| 			} | ||||
| 			reqBodyBytes, err := json.Marshal(reqBody) | ||||
|  | ||||
| 			reqBodyBytes, err := json.Marshal(tc.reqBody) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
| 			// Create test request | ||||
| 			req, err := createTestRequest(http.MethodPost, "/api/jobs/create", bytes.NewReader(reqBodyBytes)) | ||||
|  | ||||
| 			req, err := createTestRequest(http.MethodPost, "/api/jobs/create", bytes.NewReader(reqBodyBytes)) // Use /api/jobs/create | ||||
| 			assert.NoError(t, err) | ||||
| 			req.Header.Set("Content-Type", "application/json") | ||||
| 			 | ||||
| 			// Content-Type is set by createTestRequest | ||||
|  | ||||
| 			// Perform the request | ||||
| 			resp, err := app.Test(req) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
|  | ||||
| 			// Check status code | ||||
| 			assert.Equal(t, tc.expectedStatus, resp.StatusCode) | ||||
| 			 | ||||
|  | ||||
| 			// Check response body | ||||
| 			body, err := io.ReadAll(resp.Body) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.JSONEq(t, tc.expectedBody, string(body)) | ||||
| 			 | ||||
|  | ||||
| 			// Verify that all expectations were met | ||||
| 			mockClient.AssertExpectations(t) | ||||
| 		}) | ||||
| @@ -523,114 +475,96 @@ func TestCreateJob(t *testing.T) { | ||||
|  | ||||
| // TestSubmitJob tests the submitJob handler | ||||
| func TestSubmitJob(t *testing.T) { | ||||
| 	// Create a test job | ||||
| 	testJob := &herojobs.Job{ | ||||
| 		JobID:    "test-job-id", | ||||
| 		CircleID: "test-circle", | ||||
| 		Topic:    "test-topic", | ||||
| 	} | ||||
| 	 | ||||
| 	// Test cases | ||||
| 	submittedJob := herojobs.NewJob() | ||||
| 	submittedJob.JobID = 10 // Assume Save populates this | ||||
| 	submittedJob.CircleID = "test-circle" | ||||
| 	submittedJob.Topic = "test-topic" | ||||
| 	submittedJob.Params = "submitted params" | ||||
| 	submittedJob.ParamsType = herojobs.ParamsTypeHeroScript | ||||
| 	submittedJob.Status = herojobs.JobStatusNew | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name           string | ||||
| 		job            *herojobs.Job | ||||
| 		connectError   error | ||||
| 		submitError    error | ||||
| 		submitResponse *herojobs.Job | ||||
| 		jobToSubmit    *herojobs.Job // This is the job in the request body | ||||
| 		storeError     error | ||||
| 		enqueueError   error | ||||
| 		expectedStatus int | ||||
| 		expectedBody   string | ||||
| 		expectedBody   string // Will be the jobToSubmit marshaled (after potential Save) | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:           "Success", | ||||
| 			job:            testJob, | ||||
| 			connectError:   nil, | ||||
| 			submitError:    nil, | ||||
| 			submitResponse: testJob, | ||||
| 			jobToSubmit:    submittedJob, | ||||
| 			storeError:     nil, | ||||
| 			enqueueError:   nil, | ||||
| 			expectedStatus: fiber.StatusOK, | ||||
| 			expectedBody:   `{"jobid":"test-job-id","circleid":"test-circle","topic":"test-topic","error":"","heroscript":"","result":"","rhaiscript":"","sessionkey":"","status":"","time_end":0,"time_scheduled":0,"time_start":0,"timeout":0}`, | ||||
| 			// The handler returns the job object from the request after Save(), Store(), Enqueue() | ||||
| 			// For the mock, the JobID from jobToSubmit will be used. | ||||
| 			expectedBody: `{"jobid":10,"circleid":"test-circle","topic":"test-topic","params":"submitted params","paramstype":"HeroScript","status":"new","sessionkey":"","result":"","error":"","timeout":60,"log":false,"timescheduled":0,"timestart":0,"timeend":0}`, | ||||
| 		}, | ||||
| 		// Removed "Connection Error" | ||||
| 		{ | ||||
| 			name:           "Connection Error", | ||||
| 			job:            testJob, | ||||
| 			connectError:   errors.New("connection error"), | ||||
| 			submitError:    nil, | ||||
| 			submitResponse: nil, | ||||
| 			name:           "StoreJob Error", | ||||
| 			jobToSubmit:    submittedJob, | ||||
| 			storeError:     errors.New("store error"), | ||||
| 			enqueueError:   nil, | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to connect to HeroJobs server: connection error"}`, | ||||
| 			expectedBody:   `{"error":"Failed to store job in Redis: store error"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Submit Error", | ||||
| 			job:            testJob, | ||||
| 			connectError:   nil, | ||||
| 			submitError:    errors.New("submit error"), | ||||
| 			submitResponse: nil, | ||||
| 			name:           "EnqueueJob Error", | ||||
| 			jobToSubmit:    submittedJob, | ||||
| 			storeError:     nil, | ||||
| 			enqueueError:   errors.New("enqueue error"), | ||||
| 			expectedStatus: fiber.StatusInternalServerError, | ||||
| 			expectedBody:   `{"error":"Failed to submit job: submit error"}`, | ||||
| 			expectedBody:   `{"error":"Failed to enqueue job: enqueue error"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Empty Job", | ||||
| 			job:            nil, | ||||
| 			connectError:   nil, | ||||
| 			submitError:    nil, | ||||
| 			submitResponse: nil, | ||||
| 			name:           "Empty Job in request (parsing error)", | ||||
| 			jobToSubmit:    nil, // Simulates empty or malformed request body | ||||
| 			expectedStatus: fiber.StatusBadRequest, | ||||
| 			expectedBody:   `{"error":"Failed to parse job data: unexpected end of JSON input"}`, | ||||
| 			expectedBody:   `{"error":"Failed to parse job data: unexpected end of JSON input"}`, // Or similar based on actual parsing | ||||
| 		}, | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			// Create a new mock client for each test | ||||
| 			mockClient := new(MockHeroJobsClient) | ||||
| 			 | ||||
| 			// Setup mock expectations - Connect is always called in the handler | ||||
| 			mockClient.On("Connect").Return(tc.connectError) | ||||
| 			 | ||||
| 			// SubmitJob and Close are only called if Connect succeeds and job is not nil | ||||
| 			if tc.connectError == nil && tc.job != nil { | ||||
| 				mockClient.On("SubmitJob", tc.job).Return(tc.submitResponse, tc.submitError) | ||||
| 				mockClient.On("Close").Return(nil) | ||||
| 			} else { | ||||
| 				// Close is still called via defer even if we return early | ||||
| 				mockClient.On("Close").Return(nil).Maybe() | ||||
| 			_, mockClient, app := setupTest() | ||||
|  | ||||
| 			// Setup mock expectations | ||||
| 			// job.Save() is called before client interactions. | ||||
| 			if tc.jobToSubmit != nil { // If job is parsable from request | ||||
| 				// We expect StoreJob to be called with the job from the request. | ||||
| 				// The JobID might be modified by Save() in a real scenario. | ||||
| 				mockClient.On("StoreJob", tc.jobToSubmit).Return(tc.storeError).Once().Maybe() | ||||
| 				if tc.storeError == nil { | ||||
| 					mockClient.On("EnqueueJob", tc.jobToSubmit).Return(tc.enqueueError).Once().Maybe() | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new handler with the mock client | ||||
| 			handler := &JobHandler{ | ||||
| 				client: mockClient, | ||||
| 			} | ||||
| 			 | ||||
| 			// Create a new app for each test | ||||
| 			app := fiber.New() | ||||
| 			api := app.Group("/api") | ||||
| 			jobs := api.Group("/jobs") | ||||
| 			jobs.Post("/submit", handler.submitJob) | ||||
| 			 | ||||
| 			// Create request body | ||||
|  | ||||
| 			var reqBodyBytes []byte | ||||
| 			var err error | ||||
| 			if tc.job != nil { | ||||
| 				reqBodyBytes, err = json.Marshal(tc.job) | ||||
| 			if tc.jobToSubmit != nil { | ||||
| 				reqBodyBytes, err = json.Marshal(tc.jobToSubmit) | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
| 			 | ||||
| 			// Create test request | ||||
| 			req, err := createTestRequest(http.MethodPost, "/api/jobs/submit", bytes.NewReader(reqBodyBytes)) | ||||
|  | ||||
| 			req, err := createTestRequest(http.MethodPost, "/api/jobs/submit", bytes.NewReader(reqBodyBytes)) // Use /api/jobs/submit | ||||
| 			assert.NoError(t, err) | ||||
| 			req.Header.Set("Content-Type", "application/json") | ||||
| 			 | ||||
| 			// Content-Type is set by createTestRequest | ||||
|  | ||||
| 			// Perform the request | ||||
| 			resp, err := app.Test(req) | ||||
| 			assert.NoError(t, err) | ||||
| 			 | ||||
|  | ||||
| 			// Check status code | ||||
| 			assert.Equal(t, tc.expectedStatus, resp.StatusCode) | ||||
| 			 | ||||
|  | ||||
| 			// Check response body | ||||
| 			body, err := io.ReadAll(resp.Body) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.JSONEq(t, tc.expectedBody, string(body)) | ||||
| 			 | ||||
|  | ||||
| 			// Verify that all expectations were met | ||||
| 			mockClient.AssertExpectations(t) | ||||
| 		}) | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -15,8 +15,8 @@ type JobDisplayInfo struct { | ||||
| 	Topic         string `json:"topic"` | ||||
| 	Status        string `json:"status"` | ||||
| 	SessionKey    string `json:"sessionkey"` | ||||
| 	HeroScript    string `json:"heroscript"` | ||||
| 	RhaiScript    string `json:"rhaiscript"` | ||||
| 	Params        string `json:"params"` | ||||
| 	ParamsType    string `json:"paramstype"` | ||||
| 	Result        string `json:"result"` | ||||
| 	Error         string `json:"error"` | ||||
| 	TimeScheduled int64  `json:"time_scheduled"` | ||||
| @@ -27,15 +27,17 @@ type JobDisplayInfo struct { | ||||
|  | ||||
| // JobHandler handles job-related page routes | ||||
| type JobHandler struct { | ||||
| 	client *herojobs.Client | ||||
| 	client *herojobs.RedisClient | ||||
| 	logger *log.Logger | ||||
| } | ||||
|  | ||||
| // NewJobHandler creates a new job handler with the provided socket path | ||||
| func NewJobHandler(socketPath string, logger *log.Logger) (*JobHandler, error) { | ||||
| 	client, err := herojobs.NewClient(socketPath) | ||||
| func NewJobHandler(redisAddr string, logger *log.Logger) (*JobHandler, error) { | ||||
| 	// Assuming SSL is false as per README example herojobs.NewRedisClient("localhost:6379", false) | ||||
| 	// This might need to be configurable later. | ||||
| 	client, err := herojobs.NewRedisClient(redisAddr, false) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create HeroJobs client: %w", err) | ||||
| 		return nil, fmt.Errorf("failed to create HeroJobs Redis client: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return &JobHandler{ | ||||
| @@ -50,7 +52,7 @@ func (h *JobHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	jobs := app.Group("/jobs") | ||||
| 	jobs.Get("/", h.getJobsPage) | ||||
| 	jobs.Get("/list", h.getJobsList) | ||||
| 	 | ||||
|  | ||||
| 	// Register the same routes under /admin/jobs for consistency | ||||
| 	adminJobs := app.Group("/admin/jobs") | ||||
| 	adminJobs.Get("/", h.getJobsPage) | ||||
| @@ -59,18 +61,13 @@ func (h *JobHandler) RegisterRoutes(app *fiber.App) { | ||||
|  | ||||
| // getJobsPage renders the jobs page | ||||
| func (h *JobHandler) getJobsPage(c *fiber.Ctx) error { | ||||
| 	// Check if we can connect to the HeroJobs server | ||||
| 	var warning string | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		warning = "Could not connect to HeroJobs server: " + err.Error() | ||||
| 		h.logger.Printf("Warning: %s", warning) | ||||
| 	} else { | ||||
| 		h.client.Close() | ||||
| 	} | ||||
|  | ||||
| 	// Assuming h.client (RedisClient) is valid if NewJobHandler succeeded. | ||||
| 	// The client is connected on creation. A Ping method could be used here for a health check if available. | ||||
| 	// The previous connect/close logic per-request is removed. | ||||
| 	var warning string // This will be empty unless a new check (e.g., Ping) sets it. | ||||
| 	return c.Render("admin/jobs", fiber.Map{ | ||||
| 		"title":   "Jobs", | ||||
| 		"warning": warning, | ||||
| 		"warning": warning, // warning will be empty for now | ||||
| 		"error":   "", | ||||
| 	}) | ||||
| } | ||||
| @@ -100,20 +97,18 @@ func (h *JobHandler) getJobsList(c *fiber.Ctx) error { | ||||
|  | ||||
| // getJobsData gets job data from the HeroJobs server | ||||
| func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, error) { | ||||
| 	// Connect to the HeroJobs server | ||||
| 	if err := h.client.Connect(); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to connect to HeroJobs server: %w", err) | ||||
| 	} | ||||
| 	defer h.client.Close() | ||||
| 	// Assuming h.client (RedisClient) is already connected (established by NewJobHandler). | ||||
| 	// It should not be closed here as it's a long-lived client. | ||||
| 	// Connect() and Close() calls per-request are removed. | ||||
|  | ||||
| 	// If circleID and topic are not provided, try to list all jobs | ||||
| 	if circleID == "" && topic == "" { | ||||
| 		// Try to get some default jobs | ||||
| 		defaultCircles := []string{"default", "system"} | ||||
| 		defaultTopics := []string{"default", "system"} | ||||
| 		 | ||||
|  | ||||
| 		var allJobs []JobDisplayInfo | ||||
| 		 | ||||
|  | ||||
| 		// Try each combination | ||||
| 		for _, circle := range defaultCircles { | ||||
| 			for _, t := range defaultTopics { | ||||
| @@ -122,22 +117,22 @@ func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, erro | ||||
| 					h.logger.Printf("Could not list jobs for circle=%s, topic=%s: %v", circle, t, err) | ||||
| 					continue | ||||
| 				} | ||||
| 				 | ||||
|  | ||||
| 				for _, jobID := range jobIDs { | ||||
| 					job, err := h.client.GetJob(jobID) | ||||
| 					if err != nil { | ||||
| 						h.logger.Printf("Error getting job %s: %v", jobID, err) | ||||
| 						continue | ||||
| 					} | ||||
| 					 | ||||
|  | ||||
| 					allJobs = append(allJobs, JobDisplayInfo{ | ||||
| 						JobID:         job.JobID, | ||||
| 						JobID:         fmt.Sprintf("%d", job.JobID), | ||||
| 						CircleID:      job.CircleID, | ||||
| 						Topic:         job.Topic, | ||||
| 						Status:        string(job.Status), | ||||
| 						SessionKey:    job.SessionKey, | ||||
| 						HeroScript:    job.HeroScript, | ||||
| 						RhaiScript:    job.RhaiScript, | ||||
| 						Params:        job.Params, | ||||
| 						ParamsType:    string(job.ParamsType), | ||||
| 						Result:        job.Result, | ||||
| 						Error:         job.Error, | ||||
| 						TimeScheduled: job.TimeScheduled, | ||||
| @@ -148,7 +143,7 @@ func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, erro | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		return allJobs, nil | ||||
| 	} else if circleID == "" || topic == "" { | ||||
| 		// If only one of the parameters is provided, we can't list jobs | ||||
| @@ -171,13 +166,13 @@ func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, erro | ||||
| 		} | ||||
|  | ||||
| 		jobInfo := JobDisplayInfo{ | ||||
| 			JobID:         job.JobID, | ||||
| 			JobID:         fmt.Sprintf("%d", job.JobID), | ||||
| 			CircleID:      job.CircleID, | ||||
| 			Topic:         job.Topic, | ||||
| 			Status:        string(job.Status), | ||||
| 			SessionKey:    job.SessionKey, | ||||
| 			HeroScript:    job.HeroScript, | ||||
| 			RhaiScript:    job.RhaiScript, | ||||
| 			Params:        job.Params, | ||||
| 			ParamsType:    string(job.ParamsType), | ||||
| 			Result:        job.Result, | ||||
| 			Error:         job.Error, | ||||
| 			TimeScheduled: job.TimeScheduled, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user