Split dashboard tests by responsibility

Replace the monolithic dashboard basic test with focused homepage, graph page, readings API, and status API tests. Move shared in-memory database schema and seed data setup into a dashboard test helper.

Commit
606d0107007095b06908efc0db3557c0a3adf35d
Author
OpenAI Codex <codex@openai.com>
Author date
Committer
OpenAI Codex <codex@openai.com>
Committer date
Changed files
roles/dashboard/t/00-homepage.t
index 00000000..11742889 000000..100644
@@ -0,0 +1,62 @@
1 Added: # -*- mode: cperl; -*-
2 Added:
3 Added: use Mojo::Base -strict;
4 Added:
5 Added: use Test2::V0;
6 Added:
7 Added: use FindBin;
8 Added: use lib "${FindBin::Bin}/../lib/";
9 Added: use lib "${FindBin::Bin}/lib/";
10 Added: use Dashboard::Test qw(test_app);
11 Added:
12 Added: my $t = test_app();
13 Added:
14 Added: $t->get_ok('/')
15 Added: ->status_is(200)
16 Added: ->element_exists('body > header.page-header')
17 Added: ->element_exists_not('main.site-main > header.page-header')
18 Added: ->element_exists('input.nav-toggle[type="checkbox"]')
19 Added: ->text_is( 'a.page-brand[href="/"]', 'FAPG' )
20 Added: ->element_exists('a.header-status-link[href="/"][data-header-status]')
21 Added: ->element_exists('a.header-status-link span.header-status-led')
22 Added: ->element_exists('[data-header-status-item="hub"]')
23 Added: ->element_exists('[data-header-status-item="ph"]')
24 Added: ->element_exists('[data-header-status-item="do"]')
25 Added: ->element_exists('[data-header-status-item="orp"]')
26 Added: ->element_exists('[data-header-status-item="ec"]')
27 Added: ->element_exists('nav.probe-nav a[href="/graphs/ph"][data-nav-probe="ph"]')
28 Added: ->element_exists('nav.probe-nav a[href="/graphs/do"][data-nav-probe="do"]')
29 Added: ->element_exists('nav.probe-nav a[href="/graphs/orp"][data-nav-probe="orp"]')
30 Added: ->element_exists('nav.probe-nav a[href="/graphs/ec"][data-nav-probe="ec"]')
31 Added: ->element_exists('footer.site-footer a[href="https://www.lafermeaquaponique.com/"]')
32 Added: ->content_like(qr/Copyright &copy; 2026 Marius Peter/)
33 Added: ->element_exists_not('section#probe-ph[data-panel="ph"]')
34 Added: ->element_exists_not('section.dashboard-card')
35 Added: ->content_like(qr/Checking sensors/)
36 Added: ->content_like(qr/data-overview-status/)
37 Added: ->content_like(qr/data-sensor-status="ph"/)
38 Added: ->content_like(qr/Last seen online/)
39 Added: ->content_like(qr/data-reading-quick-probe="ph"/)
40 Added: ->content_like(qr/data-reading-quick-probe="do"/)
41 Added: ->content_like(qr/data-reading-quick-probe="orp"/)
42 Added: ->content_like(qr/data-reading-quick-probe="ec"/)
43 Added: ->element_exists('a.reading-quick-link[href="/graphs/ph"] article[data-reading-quick-probe="ph"]')
44 Added: ->element_exists('a.reading-quick-link[href="/graphs/do"] article[data-reading-quick-probe="do"]')
45 Added: ->element_exists(
46 Added: 'a.reading-quick-link[href="/graphs/orp"] article[data-reading-quick-probe="orp"]')
47 Added: ->element_exists('a.reading-quick-link[href="/graphs/ec"] article[data-reading-quick-probe="ec"]')
48 Added: ->element_exists_not('article[data-reading-quick-probe="ph"] h3 a')
49 Added: ->content_like(qr/Dissolved Oxygen/)
50 Added: ->content_unlike(qr/Hourly/)
51 Added: ->content_unlike(qr/Daily/)
52 Added: ->content_unlike(qr/Weekly/)
53 Added: ->content_unlike(qr/Yearly/)
54 Added: ->content_unlike(qr/data-smoothing-toggle checked/)
55 Added: ->content_unlike(qr/smoothing/)
56 Added: ->content_unlike(qr/Last 10 pH Reading Messages/)
57 Added: ->content_unlike(qr/Last 10 Dissolved Oxygen Reading Messages/)
58 Added: ->content_unlike(qr/status-pill-list/)
59 Added: ->content_unlike(qr/Pi Zero 01/)
60 Added: ->content_unlike(qr/Connection Status/);
61 Added:
62 Added: done_testing;
roles/dashboard/t/01-graph-page.t
index 00000000..24044e50 000000..100644
@@ -0,0 +1,42 @@
1 Added: # -*- mode: cperl; -*-
2 Added:
3 Added: use Mojo::Base -strict;
4 Added:
5 Added: use Test2::V0;
6 Added:
7 Added: use FindBin;
8 Added: use lib "${FindBin::Bin}/../lib/";
9 Added: use lib "${FindBin::Bin}/lib/";
10 Added: use Dashboard::Test qw(test_app);
11 Added:
12 Added: my $t = test_app();
13 Added:
14 Added: $t->get_ok('/graphs/ph')
15 Added: ->status_is(200)
16 Added: ->text_is( 'a.page-brand[href="/"]', 'FAPG' )
17 Added: ->element_exists('a.header-status-link[href="/"][data-header-status]')
18 Added: ->element_exists('a.header-status-link span.header-status-led')
19 Added: ->element_exists('[data-header-status-item="hub"]')
20 Added: ->element_exists('[data-header-status-item="ph"]')
21 Added: ->element_exists('[data-header-status-item="do"]')
22 Added: ->element_exists('[data-header-status-item="orp"]')
23 Added: ->element_exists('[data-header-status-item="ec"]')
24 Added: ->element_exists('nav.probe-nav a[href="/graphs/ph"][data-nav-probe="ph"]')
25 Added: ->element_exists('nav.probe-nav a[href="/graphs/do"][data-nav-probe="do"]')
26 Added: ->element_exists('nav.probe-nav a[href="/graphs/orp"][data-nav-probe="orp"]')
27 Added: ->element_exists('nav.probe-nav a[href="/graphs/ec"][data-nav-probe="ec"]')
28 Added: ->element_exists('footer.site-footer a[href="https://www.lafermeaquaponique.com/"]')
29 Added: ->element_exists('section#probe-ph[data-panel="ph"]')
30 Added: ->element_exists('canvas#chart-ph')
31 Added: ->element_exists_not('section#probe-do[data-panel="do"]')
32 Added: ->content_unlike(qr/Sensor Reading Quick Status/)
33 Added: ->content_like(qr/Hourly/)
34 Added: ->content_like(qr/Daily/)
35 Added: ->content_like(qr/Weekly/)
36 Added: ->content_like(qr/Yearly/)
37 Added: ->content_like(qr/data-smoothing-toggle checked/)
38 Added: ->content_like(qr/Latest pH Reading Messages/);
39 Added:
40 Added: $t->get_ok('/graphs/unknown')->status_is(404);
41 Added:
42 Added: done_testing;
roles/dashboard/t/02-readings-api.t
index 00000000..19bd4ff0 000000..100644
@@ -0,0 +1,37 @@
1 Added: # -*- mode: cperl; -*-
2 Added:
3 Added: use Mojo::Base -strict;
4 Added:
5 Added: use Test2::V0;
6 Added:
7 Added: use FindBin;
8 Added: use lib "${FindBin::Bin}/../lib/";
9 Added: use lib "${FindBin::Bin}/lib/";
10 Added: use Dashboard::Test qw(test_app);
11 Added:
12 Added: my $t = test_app();
13 Added:
14 Added: $t->get_ok('/api/readings/ph')
15 Added: ->status_is(200)
16 Added: ->json_is( '/probe' => 'ph' )
17 Added: ->json_is( '/readings/0/received_at' => '2026-07-05T12:00:10Z' )
18 Added: ->json_is( '/readings/0/value' => 7.12 )
19 Added: ->json_hasnt('/readings/1');
20 Added:
21 Added: $t->get_ok('/api/readings/ph?since=2026-07-05T12:00:05Z')
22 Added: ->status_is(200)
23 Added: ->json_is( '/readings/0/value' => 7.12 )
24 Added: ->json_hasnt('/readings/1');
25 Added:
26 Added: $t->get_ok('/api/readings/ph?since=2026-07-05T12:00:15Z')
27 Added: ->status_is(200)
28 Added: ->json_hasnt('/readings/0');
29 Added:
30 Added: $t->get_ok('/api/readings/do')
31 Added: ->status_is(200)
32 Added: ->json_is( '/probe' => 'do' )
33 Added: ->json_is( '/readings/0/value' => 8.34 );
34 Added:
35 Added: $t->get_ok('/api/readings/nope')->status_is(404);
36 Added:
37 Added: done_testing;
roles/dashboard/t/03-status-api.t
index 00000000..29b1621f 000000..100644
@@ -0,0 +1,33 @@
1 Added: # -*- mode: cperl; -*-
2 Added:
3 Added: use Mojo::Base -strict;
4 Added:
5 Added: use Test2::V0;
6 Added:
7 Added: use FindBin;
8 Added: use lib "${FindBin::Bin}/../lib/";
9 Added: use lib "${FindBin::Bin}/lib/";
10 Added: use Dashboard::Test qw(test_app);
11 Added:
12 Added: my $t = test_app();
13 Added:
14 Added: $t->get_ok('/api/status/ph')
15 Added: ->status_is(200)
16 Added: ->json_is( '/probe' => 'ph' )
17 Added: ->json_is( '/status/status' => 'ok' );
18 Added:
19 Added: $t->get_ok('/api/status/hub')
20 Added: ->status_is(200)
21 Added: ->json_is( '/probe' => 'hub' )
22 Added: ->json_is( '/status/status' => 'ok' )
23 Added: ->json_is( '/status/node' => 'fapg-daq-five-01' );
24 Added:
25 Added: $t->get_ok('/api/status/do')
26 Added: ->status_is(200)
27 Added: ->json_is( '/probe' => 'do' )
28 Added: ->json_is( '/status/status' => 'unreachable' )
29 Added: ->json_is( '/status/message' => 'No status received in the last 120 seconds' );
30 Added:
31 Added: $t->get_ok('/api/status/nope')->status_is(404);
32 Added:
33 Added: done_testing;
roles/dashboard/t/basic.t
index 99531d34..00000000 100644..000000
@@ -1,254 +0,0 @@
1 Removed: # -*- mode: cperl; -*-
2 Removed:
3 Removed: use Mojo::Base -strict;
4 Removed:
5 Removed: use Test2::V0;
6 Removed: use Test::Mojo;
7 Removed:
8 Removed: use FindBin;
9 Removed: use lib "${FindBin::Bin}/../lib/";
10 Removed: use dashboard;
11 Removed: use POSIX qw(strftime);
12 Removed:
13 Removed: $ENV{FAPG_DAQ_DB} = ':memory:';
14 Removed:
15 Removed: my $t = Test::Mojo->new('dashboard');
16 Removed: my $fresh_status_at = utc_timestamp(time);
17 Removed: my $stale_status_at = utc_timestamp( time - 121 );
18 Removed:
19 Removed: $t->app->sqlite->db->query(
20 Removed: q{
21 Removed: CREATE TABLE readings (
22 Removed: id INTEGER PRIMARY KEY AUTOINCREMENT,
23 Removed: received_at TEXT NOT NULL,
24 Removed: timestamp TEXT NOT NULL,
25 Removed: node TEXT NOT NULL,
26 Removed: probe TEXT NOT NULL,
27 Removed: value REAL NOT NULL,
28 Removed: unit TEXT,
29 Removed: raw_json TEXT
30 Removed: )
31 Removed: }
32 Removed: );
33 Removed:
34 Removed: $t->app->sqlite->db->query(
35 Removed: q{
36 Removed: CREATE TABLE node_status (
37 Removed: id INTEGER PRIMARY KEY AUTOINCREMENT,
38 Removed: received_at TEXT NOT NULL,
39 Removed: topic TEXT NOT NULL,
40 Removed: schema TEXT,
41 Removed: timestamp TEXT,
42 Removed: probe TEXT,
43 Removed: node TEXT,
44 Removed: status TEXT,
45 Removed: message TEXT,
46 Removed: error TEXT,
47 Removed: device TEXT,
48 Removed: source TEXT,
49 Removed: payload TEXT NOT NULL,
50 Removed: valid INTEGER NOT NULL DEFAULT 1
51 Removed: )
52 Removed: }
53 Removed: );
54 Removed:
55 Removed: $t->app->sqlite->db->query(
56 Removed: q{
57 Removed: INSERT INTO readings (received_at, timestamp, node, probe, value, unit)
58 Removed: VALUES (?, ?, ?, ?, ?, ?)
59 Removed: },
60 Removed: '2026-07-05T12:00:10Z',
61 Removed: '2026-07-05T12:00:00Z',
62 Removed: 'fapg-daq-zero-ph-01',
63 Removed: 'ph',
64 Removed: 7.12,
65 Removed: 'pH'
66 Removed: );
67 Removed:
68 Removed: $t->app->sqlite->db->query(
69 Removed: q{
70 Removed: INSERT INTO readings (received_at, timestamp, node, probe, value, unit)
71 Removed: VALUES (?, ?, ?, ?, ?, ?)
72 Removed: },
73 Removed: '2026-07-05T12:00:20Z',
74 Removed: '2026-07-05T12:00:15Z',
75 Removed: 'fapg-daq-zero-do-01',
76 Removed: 'do',
77 Removed: 8.34,
78 Removed: 'mg/L'
79 Removed: );
80 Removed:
81 Removed: $t->app->sqlite->db->query(
82 Removed: q{
83 Removed: INSERT INTO node_status (
84 Removed: received_at, topic, schema, timestamp, probe, node, status, message, payload
85 Removed: ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
86 Removed: },
87 Removed: $fresh_status_at,
88 Removed: 'fapg/daq/ph/fapg-daq-zero-ph-01/status',
89 Removed: 'fapg.daq.status.v1',
90 Removed: $fresh_status_at,
91 Removed: 'ph',
92 Removed: 'fapg-daq-zero-ph-01',
93 Removed: 'ok',
94 Removed: 'Probe reading valid',
95 Removed: '{}',
96 Removed: );
97 Removed:
98 Removed: $t->app->sqlite->db->query(
99 Removed: q{
100 Removed: INSERT INTO node_status (
101 Removed: received_at, topic, schema, timestamp, probe, node, status, message, payload
102 Removed: ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
103 Removed: },
104 Removed: $stale_status_at,
105 Removed: 'fapg/daq/do/fapg-daq-zero-do-01/status',
106 Removed: 'fapg.daq.status.v1',
107 Removed: $stale_status_at,
108 Removed: 'do',
109 Removed: 'fapg-daq-zero-do-01',
110 Removed: 'ok',
111 Removed: 'Probe reading valid',
112 Removed: '{}',
113 Removed: );
114 Removed:
115 Removed: $t->app->sqlite->db->query(
116 Removed: q{
117 Removed: INSERT INTO node_status (
118 Removed: received_at, topic, schema, timestamp, probe, node, status, message, payload
119 Removed: ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
120 Removed: },
121 Removed: $fresh_status_at,
122 Removed: 'fapg/daq/hub/fapg-daq-five-01/status',
123 Removed: 'fapg.daq.status.v1',
124 Removed: $fresh_status_at,
125 Removed: 'hub',
126 Removed: 'fapg-daq-five-01',
127 Removed: 'ok',
128 Removed: 'DAQ hub MQTT broker reachable',
129 Removed: '{}',
130 Removed: );
131 Removed:
132 Removed: $t->get_ok('/')
133 Removed: ->status_is(200)
134 Removed: ->element_exists('body > header.page-header')
135 Removed: ->element_exists_not('main.site-main > header.page-header')
136 Removed: ->element_exists('input.nav-toggle[type="checkbox"]')
137 Removed: ->text_is('a.page-brand[href="/"]', 'FAPG')
138 Removed: ->element_exists('a.header-status-link[href="/"][data-header-status]')
139 Removed: ->element_exists('a.header-status-link span.header-status-led')
140 Removed: ->element_exists('[data-header-status-item="hub"]')
141 Removed: ->element_exists('[data-header-status-item="ph"]')
142 Removed: ->element_exists('[data-header-status-item="do"]')
143 Removed: ->element_exists('[data-header-status-item="orp"]')
144 Removed: ->element_exists('[data-header-status-item="ec"]')
145 Removed: ->element_exists('nav.probe-nav a[href="/graphs/ph"][data-nav-probe="ph"]')
146 Removed: ->element_exists('nav.probe-nav a[href="/graphs/do"][data-nav-probe="do"]')
147 Removed: ->element_exists('nav.probe-nav a[href="/graphs/orp"][data-nav-probe="orp"]')
148 Removed: ->element_exists('nav.probe-nav a[href="/graphs/ec"][data-nav-probe="ec"]')
149 Removed: ->element_exists('footer.site-footer a[href="https://www.lafermeaquaponique.com/"]')
150 Removed: ->content_like(qr/Copyright &copy; 2026 Marius Peter/)
151 Removed: ->element_exists_not('section#probe-ph[data-panel="ph"]')
152 Removed: ->element_exists_not('section.dashboard-card')
153 Removed: ->content_like(qr/Checking sensors/)
154 Removed: ->content_like(qr/data-overview-status/)
155 Removed: ->content_like(qr/data-sensor-status="ph"/)
156 Removed: ->content_like(qr/Last seen online/)
157 Removed: ->content_like(qr/Sensor Reading Quick Status/)
158 Removed: ->content_like(qr/data-reading-quick-probe="ph"/)
159 Removed: ->content_like(qr/data-reading-quick-probe="do"/)
160 Removed: ->content_like(qr/data-reading-quick-probe="orp"/)
161 Removed: ->content_like(qr/data-reading-quick-probe="ec"/)
162 Removed: ->element_exists('a.reading-quick-link[href="/graphs/ph"] article[data-reading-quick-probe="ph"]')
163 Removed: ->element_exists('a.reading-quick-link[href="/graphs/do"] article[data-reading-quick-probe="do"]')
164 Removed: ->element_exists('a.reading-quick-link[href="/graphs/orp"] article[data-reading-quick-probe="orp"]')
165 Removed: ->element_exists('a.reading-quick-link[href="/graphs/ec"] article[data-reading-quick-probe="ec"]')
166 Removed: ->element_exists_not('article[data-reading-quick-probe="ph"] h3 a')
167 Removed: ->content_like(qr/Dissolved Oxygen/)
168 Removed: ->content_unlike(qr/Hourly/)
169 Removed: ->content_unlike(qr/Daily/)
170 Removed: ->content_unlike(qr/Weekly/)
171 Removed: ->content_unlike(qr/Yearly/)
172 Removed: ->content_unlike(qr/data-smoothing-toggle checked/)
173 Removed: ->content_unlike(qr/smoothing/)
174 Removed: ->content_unlike(qr/Last 10 pH Reading Messages/)
175 Removed: ->content_unlike(qr/Last 10 Dissolved Oxygen Reading Messages/)
176 Removed: ->content_unlike(qr/status-pill-list/)
177 Removed: ->content_unlike(qr/Pi Zero 01/)
178 Removed: ->content_unlike(qr/Connection Status/);
179 Removed:
180 Removed: $t->get_ok('/graphs/ph')
181 Removed: ->status_is(200)
182 Removed: ->text_is('a.page-brand[href="/"]', 'FAPG')
183 Removed: ->element_exists('a.header-status-link[href="/"][data-header-status]')
184 Removed: ->element_exists('a.header-status-link span.header-status-led')
185 Removed: ->element_exists('[data-header-status-item="hub"]')
186 Removed: ->element_exists('[data-header-status-item="ph"]')
187 Removed: ->element_exists('[data-header-status-item="do"]')
188 Removed: ->element_exists('[data-header-status-item="orp"]')
189 Removed: ->element_exists('[data-header-status-item="ec"]')
190 Removed: ->element_exists('nav.probe-nav a[href="/graphs/ph"][data-nav-probe="ph"]')
191 Removed: ->element_exists('nav.probe-nav a[href="/graphs/do"][data-nav-probe="do"]')
192 Removed: ->element_exists('nav.probe-nav a[href="/graphs/orp"][data-nav-probe="orp"]')
193 Removed: ->element_exists('nav.probe-nav a[href="/graphs/ec"][data-nav-probe="ec"]')
194 Removed: ->element_exists('footer.site-footer a[href="https://www.lafermeaquaponique.com/"]')
195 Removed: ->element_exists('section#probe-ph[data-panel="ph"]')
196 Removed: ->element_exists('canvas#chart-ph')
197 Removed: ->element_exists_not('section#probe-do[data-panel="do"]')
198 Removed: ->content_unlike(qr/Sensor Reading Quick Status/)
199 Removed: ->content_like(qr/Hourly/)
200 Removed: ->content_like(qr/Daily/)
201 Removed: ->content_like(qr/Weekly/)
202 Removed: ->content_like(qr/Yearly/)
203 Removed: ->content_like(qr/data-smoothing-toggle checked/)
204 Removed: ->content_like(qr/Latest pH Reading Messages/);
205 Removed:
206 Removed: $t->get_ok('/graphs/unknown')->status_is(404);
207 Removed:
208 Removed: $t->get_ok('/api/readings/ph')
209 Removed: ->status_is(200)
210 Removed: ->json_is( '/probe' => 'ph' )
211 Removed: ->json_is( '/readings/0/received_at' => '2026-07-05T12:00:10Z' )
212 Removed: ->json_is( '/readings/0/value' => 7.12 )
213 Removed: ->json_hasnt('/readings/1');
214 Removed:
215 Removed: $t->get_ok('/api/readings/ph?since=2026-07-05T12:00:05Z')
216 Removed: ->status_is(200)
217 Removed: ->json_is( '/readings/0/value' => 7.12 )
218 Removed: ->json_hasnt('/readings/1');
219 Removed:
220 Removed: $t->get_ok('/api/readings/ph?since=2026-07-05T12:00:15Z')
221 Removed: ->status_is(200)
222 Removed: ->json_hasnt('/readings/0');
223 Removed:
224 Removed: $t->get_ok('/api/readings/do')
225 Removed: ->status_is(200)
226 Removed: ->json_is( '/probe' => 'do' )
227 Removed: ->json_is( '/readings/0/value' => 8.34 );
228 Removed:
229 Removed: $t->get_ok('/api/status/ph')
230 Removed: ->status_is(200)
231 Removed: ->json_is( '/probe' => 'ph' )
232 Removed: ->json_is( '/status/status' => 'ok' );
233 Removed:
234 Removed: $t->get_ok('/api/status/hub')
235 Removed: ->status_is(200)
236 Removed: ->json_is( '/probe' => 'hub' )
237 Removed: ->json_is( '/status/status' => 'ok' )
238 Removed: ->json_is( '/status/node' => 'fapg-daq-five-01' );
239 Removed:
240 Removed: $t->get_ok('/api/status/do')
241 Removed: ->status_is(200)
242 Removed: ->json_is( '/probe' => 'do' )
243 Removed: ->json_is( '/status/status' => 'unreachable' )
244 Removed: ->json_is( '/status/message' => 'No status received in the last 120 seconds' );
245 Removed:
246 Removed: $t->get_ok('/api/readings/nope')->status_is(404);
247 Removed: $t->get_ok('/api/status/nope')->status_is(404);
248 Removed:
249 Removed: done_testing;
250 Removed:
251 Removed: sub utc_timestamp {
252 Removed: my ($epoch) = @_;
253 Removed: return strftime( '%Y-%m-%dT%H:%M:%SZ', gmtime $epoch );
254 Removed: }
roles/dashboard/t/lib/Dashboard/Test.pm
index 00000000..5125b66a 000000..100644
@@ -0,0 +1,149 @@
1 Added: # -*- mode: cperl; -*-
2 Added:
3 Added: package Dashboard::Test;
4 Added: use Mojo::Base -strict;
5 Added:
6 Added: use Exporter qw(import);
7 Added: use POSIX qw(strftime);
8 Added: use Test::Mojo;
9 Added:
10 Added: use dashboard;
11 Added:
12 Added: our @EXPORT_OK = qw(test_app);
13 Added:
14 Added: sub test_app {
15 Added: $ENV{FAPG_DAQ_DB} = ':memory:';
16 Added:
17 Added: my $t = Test::Mojo->new('dashboard');
18 Added: my $fresh_status_at = utc_timestamp(time);
19 Added: my $stale_status_at = utc_timestamp( time - 121 );
20 Added:
21 Added: create_schema($t);
22 Added: seed_readings($t);
23 Added: seed_statuses( $t, $fresh_status_at, $stale_status_at );
24 Added:
25 Added: return $t;
26 Added: }
27 Added:
28 Added: sub create_schema {
29 Added: my ($t) = @_;
30 Added:
31 Added: $t->app->sqlite->db->query(
32 Added: q{
33 Added: CREATE TABLE readings (
34 Added: id INTEGER PRIMARY KEY AUTOINCREMENT,
35 Added: received_at TEXT NOT NULL,
36 Added: timestamp TEXT NOT NULL,
37 Added: node TEXT NOT NULL,
38 Added: probe TEXT NOT NULL,
39 Added: value REAL NOT NULL,
40 Added: unit TEXT,
41 Added: raw_json TEXT
42 Added: )
43 Added: }
44 Added: );
45 Added:
46 Added: $t->app->sqlite->db->query(
47 Added: q{
48 Added: CREATE TABLE node_status (
49 Added: id INTEGER PRIMARY KEY AUTOINCREMENT,
50 Added: received_at TEXT NOT NULL,
51 Added: topic TEXT NOT NULL,
52 Added: schema TEXT,
53 Added: timestamp TEXT,
54 Added: probe TEXT,
55 Added: node TEXT,
56 Added: status TEXT,
57 Added: message TEXT,
58 Added: error TEXT,
59 Added: device TEXT,
60 Added: source TEXT,
61 Added: payload TEXT NOT NULL,
62 Added: valid INTEGER NOT NULL DEFAULT 1
63 Added: )
64 Added: }
65 Added: );
66 Added:
67 Added: return;
68 Added: }
69 Added:
70 Added: sub seed_readings {
71 Added: my ($t) = @_;
72 Added:
73 Added: $t->app->sqlite->db->query(
74 Added: q{
75 Added: INSERT INTO readings (received_at, timestamp, node, probe, value, unit)
76 Added: VALUES (?, ?, ?, ?, ?, ?)
77 Added: },
78 Added: '2026-07-05T12:00:10Z',
79 Added: '2026-07-05T12:00:00Z',
80 Added: 'fapg-daq-zero-ph-01',
81 Added: 'ph',
82 Added: 7.12,
83 Added: 'pH'
84 Added: );
85 Added:
86 Added: $t->app->sqlite->db->query(
87 Added: q{
88 Added: INSERT INTO readings (received_at, timestamp, node, probe, value, unit)
89 Added: VALUES (?, ?, ?, ?, ?, ?)
90 Added: },
91 Added: '2026-07-05T12:00:20Z',
92 Added: '2026-07-05T12:00:15Z',
93 Added: 'fapg-daq-zero-do-01',
94 Added: 'do',
95 Added: 8.34,
96 Added: 'mg/L'
97 Added: );
98 Added:
99 Added: return;
100 Added: }
101 Added:
102 Added: sub seed_statuses {
103 Added: my ( $t, $fresh_status_at, $stale_status_at ) = @_;
104 Added:
105 Added: insert_status( $t, $fresh_status_at, 'fapg/daq/ph/fapg-daq-zero-ph-01/status',
106 Added: 'fapg.daq.status.v1', $fresh_status_at, 'ph', 'fapg-daq-zero-ph-01', 'ok',
107 Added: 'Probe reading valid', '{}', );
108 Added:
109 Added: insert_status( $t, $stale_status_at, 'fapg/daq/do/fapg-daq-zero-do-01/status',
110 Added: 'fapg.daq.status.v1', $stale_status_at, 'do', 'fapg-daq-zero-do-01', 'ok',
111 Added: 'Probe reading valid', '{}', );
112 Added:
113 Added: insert_status( $t, $fresh_status_at, 'fapg/daq/hub/fapg-daq-five-01/status',
114 Added: 'fapg.daq.status.v1', $fresh_status_at, 'hub', 'fapg-daq-five-01', 'ok',
115 Added: 'DAQ hub MQTT broker reachable', '{}', );
116 Added:
117 Added: return;
118 Added: }
119 Added:
120 Added: sub insert_status {
121 Added: my ( $t, $received_at, $topic, $schema, $timestamp, $probe, $node, $status, $message, $payload )
122 Added: = @_;
123 Added:
124 Added: $t->app->sqlite->db->query(
125 Added: q{
126 Added: INSERT INTO node_status (
127 Added: received_at, topic, schema, timestamp, probe, node, status, message, payload
128 Added: ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
129 Added: },
130 Added: $received_at,
131 Added: $topic,
132 Added: $schema,
133 Added: $timestamp,
134 Added: $probe,
135 Added: $node,
136 Added: $status,
137 Added: $message,
138 Added: $payload,
139 Added: );
140 Added:
141 Added: return;
142 Added: }
143 Added:
144 Added: sub utc_timestamp {
145 Added: my ($epoch) = @_;
146 Added: return strftime( '%Y-%m-%dT%H:%M:%SZ', gmtime $epoch );
147 Added: }
148 Added:
149 Added: 1;