[Perl] DAQ system for the FAPG.
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.
Changed files
roles/dashboard/t/00-homepage.t
@@ -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 © 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
@@ -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
@@ -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
@@ -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
@@ -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 © 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
@@ -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;