Search Apps Documentation Source Content File Folder Download Copy Actions Download

nightsky_test.gno

7.69 Kb · 271 lines
  1package nightsky
  2
  3import (
  4	"testing"
  5	"time"
  6
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/nt/uassert/v0"
  9	"gno.land/p/nt/urequire/v0"
 10)
 11
 12const (
 13	owner = "g1owner"
 14	guest = "g1guest"
 15)
 16
 17func newTestRealm() *TelescopeRealm {
 18	cfg := NewTelescopeConfig("Test Scope", "Seestar S50", 49.0, 1.0, owner, "/r/test/telescope")
 19	return NewTelescopeRealm(&cfg)
 20}
 21
 22func TestNewTelescopeConfig(t *testing.T) {
 23	cfg := NewTelescopeConfig("Scope", "Model", 1.5, 2.5, owner, "/r/x/y")
 24	uassert.Equal(t, "Scope", cfg.Name)
 25	uassert.Equal(t, "offline", cfg.Status) // starts offline
 26	uassert.Equal(t, owner, cfg.Owner)
 27
 28	cfg.UpdateStatus("online")
 29	uassert.Equal(t, "online", cfg.Status)
 30}
 31
 32func TestValidateCommand(cur realm, t *testing.T) {
 33	cfg := NewTelescopeConfig("Scope", "Model", 0, 0, owner, "/r/x/y")
 34
 35	// Valid capture and stop should not panic.
 36	uassert.NotPanics(t, cur, func() {
 37		ValidateCommand(TelescopeCommand{CommandType: "capture", TargetRA: 5.5, TargetDec: 22.5, Exposure: 60}, cfg)
 38	})
 39	uassert.NotPanics(t, cur, func() {
 40		// stop ignores coordinates entirely.
 41		ValidateCommand(TelescopeCommand{CommandType: "stop", TargetRA: 999, TargetDec: 999, Exposure: 0}, cfg)
 42	})
 43
 44	cases := []struct {
 45		name string
 46		cmd  TelescopeCommand
 47		msg  string
 48	}{
 49		{"bad type", TelescopeCommand{CommandType: "explode"}, "invalid command type"},
 50		{"RA low", TelescopeCommand{CommandType: "capture", TargetRA: -1, Exposure: 10}, "invalid RA"},
 51		{"RA high", TelescopeCommand{CommandType: "capture", TargetRA: 24, Exposure: 10}, "invalid RA"},
 52		{"Dec low", TelescopeCommand{CommandType: "capture", TargetRA: 1, TargetDec: -91, Exposure: 10}, "invalid Dec"},
 53		{"Dec high", TelescopeCommand{CommandType: "capture", TargetRA: 1, TargetDec: 91, Exposure: 10}, "invalid Dec"},
 54		{"exp low", TelescopeCommand{CommandType: "capture", TargetRA: 1, Exposure: 0}, "invalid exposure"},
 55		{"exp high", TelescopeCommand{CommandType: "capture", TargetRA: 1, Exposure: 301}, "invalid exposure"},
 56	}
 57	for _, tc := range cases {
 58		cmd := tc.cmd
 59		uassert.PanicsContains(t, cur, tc.msg, func() { ValidateCommand(cmd, cfg) }, tc.name)
 60	}
 61}
 62
 63func TestIsAccessAllowed(t *testing.T) {
 64	rules := avl.NewTree()
 65
 66	// Unknown address: denied.
 67	uassert.False(t, IsAccessAllowed(guest, rules))
 68
 69	// Never-expires rule (zero time): allowed.
 70	rules.Set(guest, AccessRule{AllowedAddress: guest, ExpiresAt: time.Time{}})
 71	uassert.True(t, IsAccessAllowed(guest, rules))
 72
 73	// Future expiry: allowed.
 74	rules.Set(guest, AccessRule{AllowedAddress: guest, ExpiresAt: time.Now().Add(time.Hour)})
 75	uassert.True(t, IsAccessAllowed(guest, rules))
 76
 77	// Past expiry: denied.
 78	rules.Set(guest, AccessRule{AllowedAddress: guest, ExpiresAt: time.Now().Add(-time.Hour)})
 79	uassert.False(t, IsAccessAllowed(guest, rules))
 80}
 81
 82func TestAccessFlow(cur realm, t *testing.T) {
 83	r := newTestRealm()
 84
 85	// Owner can always submit.
 86	urequire.NotPanics(t, cur, func() {
 87		r.SubmitCommand(owner, "capture", 5, 10, 30)
 88	})
 89
 90	// Guest without a grant is denied.
 91	uassert.PanicsContains(t, cur, "access denied", func() {
 92		r.SubmitCommand(guest, "capture", 5, 10, 30)
 93	})
 94
 95	// Grant, then guest can submit.
 96	r.GrantAccess(owner, guest, 0) // 0 = never expires
 97	uassert.Equal(t, 1, r.GetAccessRuleCount())
 98	urequire.NotPanics(t, cur, func() {
 99		r.SubmitCommand(guest, "capture", 5, 10, 30)
100	})
101
102	// Re-granting the same address does not duplicate the rule.
103	r.GrantAccess(owner, guest, 5)
104	uassert.Equal(t, 1, r.GetAccessRuleCount())
105
106	// Revoke, then guest is denied again.
107	r.RevokeAccess(owner, guest)
108	uassert.Equal(t, 0, r.GetAccessRuleCount())
109	uassert.PanicsContains(t, cur, "access denied", func() {
110		r.SubmitCommand(guest, "capture", 5, 10, 30)
111	})
112
113	// Non-owner cannot grant access.
114	uassert.PanicsContains(t, cur, "owner only", func() {
115		r.GrantAccess(guest, "g1evil", 0)
116	})
117}
118
119func TestCommandQueueFIFO(cur realm, t *testing.T) {
120	r := newTestRealm()
121
122	r.SubmitCommand(owner, "capture", 1, 1, 10)
123	r.SubmitCommand(owner, "capture", 2, 2, 20)
124	r.SubmitCommand(owner, "stop", 0, 0, 0)
125	uassert.Equal(t, 3, r.GetCommandCount())
126
127	// FIFO order.
128	c1 := r.GetNextCommand(owner)
129	uassert.Equal(t, "capture", c1.CommandType)
130	uassert.Equal(t, int64(10), int64(c1.Exposure))
131
132	c2 := r.GetNextCommand(owner)
133	uassert.Equal(t, int64(20), int64(c2.Exposure))
134
135	c3 := r.GetNextCommand(owner)
136	uassert.Equal(t, "stop", c3.CommandType)
137
138	uassert.Equal(t, 0, r.GetCommandCount())
139
140	// Draining an empty queue panics.
141	uassert.PanicsContains(t, cur, "no commands", func() { r.GetNextCommand(owner) })
142
143	// Only the owner may pull commands.
144	r.SubmitCommand(owner, "stop", 0, 0, 0)
145	uassert.PanicsContains(t, cur, "owner only", func() { r.GetNextCommand(guest) })
146
147	// Emergency stop clears everything and sets status online.
148	r.ClearCommandQueue(owner)
149	uassert.Equal(t, 0, r.GetCommandCount())
150	uassert.Equal(t, "online", r.Config.Status)
151}
152
153func TestRecordCaptureUpdatesStatus(cur realm, t *testing.T) {
154	r := newTestRealm()
155
156	// Submitting a command flips status to busy.
157	r.SubmitCommand(owner, "capture", 5, 10, 30)
158	uassert.Equal(t, "busy", r.Config.Status)
159
160	// Recording a capture flips it back to online and stores the capture.
161	r.RecordCapture(owner, "https://img/1.png", 5, 10, 30, owner)
162	uassert.Equal(t, "online", r.Config.Status)
163	uassert.Equal(t, 1, r.GetCaptureCount())
164
165	// Empty image URL is rejected.
166	uassert.PanicsContains(t, cur, "image URL", func() {
167		r.RecordCapture(owner, "", 5, 10, 30, owner)
168	})
169
170	// Non-owner cannot record.
171	uassert.PanicsContains(t, cur, "owner only", func() {
172		r.RecordCapture(guest, "https://img/2.png", 5, 10, 30, guest)
173	})
174}
175
176// --- CaptureFeed tests ------------------------------------------------------
177
178func mkCap(url string) CaptureResult {
179	return CaptureResult{ImageURL: url}
180}
181
182func TestCaptureFeedOrderAndLatest(t *testing.T) {
183	f := NewCaptureFeed(10)
184	uassert.Equal(t, 0, f.Len())
185
186	_, ok := f.Latest()
187	uassert.False(t, ok)
188
189	f.Add(mkCap("a"))
190	f.Add(mkCap("b"))
191	f.Add(mkCap("c"))
192	uassert.Equal(t, 3, f.Len())
193
194	latest, ok := f.Latest()
195	urequire.True(t, ok)
196	uassert.Equal(t, "c", latest.ImageURL)
197
198	// Newest-first iteration order.
199	var got []string
200	f.IterateNewest(func(c CaptureResult) bool {
201		got = append(got, c.ImageURL)
202		return false
203	})
204	urequire.Equal(t, 3, len(got))
205	uassert.Equal(t, "c", got[0])
206	uassert.Equal(t, "b", got[1])
207	uassert.Equal(t, "a", got[2])
208
209	// Early stop.
210	count := 0
211	f.IterateNewest(func(c CaptureResult) bool {
212		count++
213		return true // stop after first
214	})
215	uassert.Equal(t, 1, count)
216}
217
218func TestCaptureFeedTrim(t *testing.T) {
219	f := NewCaptureFeed(3)
220	f.Add(mkCap("1"))
221	f.Add(mkCap("2"))
222	f.Add(mkCap("3"))
223	f.Add(mkCap("4")) // evicts "1"
224	f.Add(mkCap("5")) // evicts "2"
225
226	uassert.Equal(t, 3, f.Len())
227
228	var got []string
229	f.IterateNewest(func(c CaptureResult) bool {
230		got = append(got, c.ImageURL)
231		return false
232	})
233	// Newest-first: 5, 4, 3 — oldest two evicted.
234	uassert.Equal(t, "5", got[0])
235	uassert.Equal(t, "4", got[1])
236	uassert.Equal(t, "3", got[2])
237}
238
239func TestCaptureFeedRemoveAt(t *testing.T) {
240	f := NewCaptureFeed(10)
241	f.Add(mkCap("a"))
242	f.Add(mkCap("b"))
243	f.Add(mkCap("c")) // newest-first: c(0), b(1), a(2)
244
245	// Out-of-range is a no-op returning false.
246	uassert.False(t, f.RemoveAt(-1))
247	uassert.False(t, f.RemoveAt(3))
248	uassert.Equal(t, 3, f.Len())
249
250	// Remove the middle (b).
251	uassert.True(t, f.RemoveAt(1))
252	uassert.Equal(t, 2, f.Len())
253
254	var got []string
255	f.IterateNewest(func(c CaptureResult) bool {
256		got = append(got, c.ImageURL)
257		return false
258	})
259	uassert.Equal(t, "c", got[0])
260	uassert.Equal(t, "a", got[1])
261
262	// Removing newest (c).
263	uassert.True(t, f.RemoveAt(0))
264	latest, ok := f.Latest()
265	urequire.True(t, ok)
266	uassert.Equal(t, "a", latest.ImageURL)
267}
268
269func TestNewCaptureFeedRejectsNonPositive(cur realm, t *testing.T) {
270	uassert.PanicsContains(t, cur, "must be positive", func() { NewCaptureFeed(0) })
271}