Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 133 additions & 4 deletions internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (r *Runner) runSetup(args []string) error {

func setupResultPayload(repo store.Repository, created bool, seedResult *store.PushResult) map[string]interface{} {
payload := map[string]interface{}{
"project": repo.Manifest,
"project": jsonProjectManifestFromDomain(repo.Manifest),
"manifestPath": repo.ManifestPath,
"keyPath": repo.KeyPath(),
"deviceId": repo.DeviceID(),
Expand All @@ -127,6 +127,135 @@ func setupResultPayload(repo store.Repository, created bool, seedResult *store.P
return payload
}

type jsonProjectManifest struct {
Schema string `json:"schema"`
ID string `json:"id"`
Name string `json:"name"`
Language string `json:"language"`
Framework string `json:"framework"`
PackageManager string `json:"packageManager"`
DeployTarget string `json:"deployTarget"`
ActivityMode string `json:"activityMode"`
AuditEnvs []string `json:"auditEnvs"`
Environments map[string]domain.Environment `json:"environments"`
ScanLevel string `json:"scanLevel"`
ScanIgnores []string `json:"scanIgnores"`
}

type jsonDeviceRecord struct {
Schema string `json:"schema"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Platform string `json:"platform,omitempty"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
SigningKey domain.PublicKeyRecord `json:"signingKey"`
EncryptionKey domain.PublicKeyRecord `json:"encryptionKey"`
}

type jsonAccessRequest struct {
Schema string `json:"schema"`
ProjectID string `json:"projectId"`
ID string `json:"id"`
DeviceID string `json:"deviceId"`
Environment string `json:"environment"`
Role string `json:"role"`
Reason string `json:"reason,omitempty"`
CreatedAt string `json:"createdAt"`
}

type jsonAccessRequestEntry struct {
Request jsonAccessRequest `json:"request"`
Device jsonDeviceRecord `json:"device"`
AccessState string `json:"accessState"`
}

type jsonInvalidAccessRequestEntry struct {
Request jsonAccessRequest `json:"request"`
Error string `json:"error"`
}

type jsonAccessRequestList struct {
Valid []jsonAccessRequestEntry `json:"valid"`
Invalid []jsonInvalidAccessRequestEntry `json:"invalid"`
}

func jsonProjectManifestFromDomain(project domain.ProjectManifest) jsonProjectManifest {
environments := map[string]domain.Environment{}
for name, env := range project.Environments {
environments[name] = env
}
return jsonProjectManifest{
Schema: project.Schema,
ID: project.ID,
Name: project.Name,
Language: project.Language,
Framework: project.Framework,
PackageManager: project.PackageManager,
DeployTarget: project.DeployTarget,
ActivityMode: project.ActivityMode,
AuditEnvs: append([]string{}, project.AuditEnvs...),
Environments: environments,
ScanLevel: project.ScanLevel,
ScanIgnores: append([]string{}, project.ScanIgnores...),
}
}

func jsonDeviceRecordFromDomain(device domain.DeviceRecord) jsonDeviceRecord {
return jsonDeviceRecord{
Schema: device.Schema,
ID: device.ID,
Name: device.Name,
Platform: device.Platform,
Status: device.Status,
CreatedAt: device.CreatedAt,
SigningKey: device.SigningKey,
EncryptionKey: device.EncryptionKey,
}
}

func jsonDeviceRecordsFromDomain(devices []domain.DeviceRecord) []jsonDeviceRecord {
result := make([]jsonDeviceRecord, 0, len(devices))
for _, device := range devices {
result = append(result, jsonDeviceRecordFromDomain(device))
}
return result
}

func jsonAccessRequestFromDomain(request domain.AccessRequest) jsonAccessRequest {
return jsonAccessRequest{
Schema: request.Schema,
ProjectID: request.ProjectID,
ID: request.ID,
DeviceID: request.DeviceID,
Environment: request.Environment,
Role: request.Role,
Reason: request.Reason,
CreatedAt: request.CreatedAt,
}
}

func jsonAccessRequestListFromStore(requests store.AccessRequestList) jsonAccessRequestList {
result := jsonAccessRequestList{
Valid: make([]jsonAccessRequestEntry, 0, len(requests.Valid)),
Invalid: make([]jsonInvalidAccessRequestEntry, 0, len(requests.Invalid)),
}
for _, entry := range requests.Valid {
result.Valid = append(result.Valid, jsonAccessRequestEntry{
Request: jsonAccessRequestFromDomain(entry.Request),
Device: jsonDeviceRecordFromDomain(entry.Device),
AccessState: entry.AccessState,
})
}
for _, entry := range requests.Invalid {
result.Invalid = append(result.Invalid, jsonInvalidAccessRequestEntry{
Request: jsonAccessRequestFromDomain(entry.Request),
Error: entry.Error,
})
}
return result
}

func (r *Runner) printSetupResult(repo store.Repository, dotenvSeed *defaultDotenvSeed, seedResult *store.PushResult) {
fmt.Fprintln(r.out, strings.TrimRight(renderGhostableBanner(), "\n"))
fmt.Fprintln(r.out)
Expand Down Expand Up @@ -236,14 +365,14 @@ func (r *Runner) runStatus(args []string) error {
}

payload := map[string]interface{}{
"project": repo.Manifest,
"project": jsonProjectManifestFromDomain(repo.Manifest),
"root": repo.Root,
"manifestPath": repo.ManifestPath,
"keyPath": repo.KeyPath(),
"deviceId": repo.DeviceID(),
"devices": devices,
"devices": jsonDeviceRecordsFromDomain(devices),
"valueCounts": counts,
"accessRequests": accessRequests,
"accessRequests": jsonAccessRequestListFromStore(accessRequests),
}
if *jsonOut {
return printJSON(r.out, payload)
Expand Down
59 changes: 59 additions & 0 deletions internal/app/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package app

import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"

"github.com/ghostable-dev/beta/internal/domain"
"github.com/ghostable-dev/beta/internal/manifest"
"github.com/ghostable-dev/beta/internal/prompt"
"github.com/ghostable-dev/beta/internal/store"
Expand Down Expand Up @@ -177,6 +179,26 @@ func TestRunSetupSeedsDefaultEnvironmentFromDotenvFlag(t *testing.T) {
if !strings.Contains(output.String(), `"seededFrom"`) {
t.Fatalf("expected setup JSON to include seeded result, got:\n%s", output.String())
}
var payload map[string]interface{}
if err := json.Unmarshal(output.Bytes(), &payload); err != nil {
t.Fatalf("parse setup JSON: %v\n%s", err, output.String())
}
project := jsonObjectForTest(t, payload, "project")
if _, exists := project["Schema"]; exists {
t.Fatalf("setup JSON should not expose raw ProjectManifest fields: %#v", project)
}
if _, exists := project["ID"]; exists {
t.Fatalf("setup JSON should not expose raw ProjectManifest fields: %#v", project)
}
if project["schema"] != domain.ProjectSchema {
t.Fatalf("expected lower-camel project schema, got %#v", project)
}
if project["name"] != "Test Project" {
t.Fatalf("expected lower-camel project name, got %#v", project)
}
if _, exists := project["packageManager"]; !exists {
t.Fatalf("expected lower-camel packageManager field, got %#v", project)
}
}

func TestRunStatusPrintsBannerAndInventory(t *testing.T) {
Expand Down Expand Up @@ -262,6 +284,43 @@ func TestRunStatusJSONDoesNotPrintBanner(t *testing.T) {
if !strings.Contains(text, `"project"`) {
t.Fatalf("expected status JSON payload:\n%s", text)
}
var payload map[string]interface{}
if err := json.Unmarshal(output.Bytes(), &payload); err != nil {
t.Fatalf("parse status JSON: %v\n%s", err, text)
}
project := jsonObjectForTest(t, payload, "project")
if _, exists := project["Schema"]; exists {
t.Fatalf("status JSON should not expose raw ProjectManifest fields: %#v", project)
}
if project["schema"] != domain.ProjectSchema {
t.Fatalf("expected lower-camel project schema, got %#v", project)
}
devices, ok := payload["devices"].([]interface{})
if !ok || len(devices) != 1 {
t.Fatalf("expected one status device, got %#v", payload["devices"])
}
device, ok := devices[0].(map[string]interface{})
if !ok {
t.Fatalf("expected status device object, got %#v", devices[0])
}
if _, exists := device["device_id"]; exists {
t.Fatalf("status JSON should not expose device_id compatibility field: %#v", device)
}
if _, exists := device["client_sig"]; exists {
t.Fatalf("status JSON should not expose client_sig signature field: %#v", device)
}
if device["schema"] != domain.DeviceSchema || device["id"] == "" {
t.Fatalf("expected stable device JSON fields, got %#v", device)
}
}

func jsonObjectForTest(t *testing.T, payload map[string]interface{}, key string) map[string]interface{} {
t.Helper()
object, ok := payload[key].(map[string]interface{})
if !ok {
t.Fatalf("expected %s object, got %#v", key, payload[key])
}
return object
}

func setupTempWorkdir(t *testing.T) string {
Expand Down