factor action execution out into solver method
[scpubgit/DKit.git] / t / dot_ssh.t
CommitLineData
9a4942da 1use strictures 1;
2use Test::More;
3use DX::Solver;
4use DX::SetOver;
5ef4d923 5use DX::Observer::FromCode;
71217e42 6use DX::Action::FromCode;
deec7cc4 7use File::Spec;
734376d9 8use Test::Exception;
9a4942da 9
10{
11 package My::PathStatus;
12
13 use Moo;
14
15 has path => (is => 'ro', required => 1);
859049a3 16 has info => (is => 'ro', predicate => 1);
9a4942da 17
18 package My::PathStatusInfo;
19
20 use Moo;
21
22 has is_directory => (is => 'ro', default => 0);
23 has is_file => (is => 'ro', default => 0);
24 has mode => (is => 'ro', required => 1);
25}
26
27@INC{qw(My/PathStatus.pm My/PathStatusInfo.pm)} = (__FILE__,__FILE__);
28
29my %protos = (
30 '.ssh' => My::PathStatus->new(
31 path => '.ssh',
32 info => My::PathStatusInfo->new(is_directory => 1, mode => '0755')
33 ),
34 '.ssh/authorized_keys' => My::PathStatus->new(
35 path => '.ssh/authorized_keys',
36 info => My::PathStatusInfo->new(is_file => 1, mode => '0644')
37 ),
38);
39
71217e42 40my %empty = (
41 '.ssh' => My::PathStatus->new(
42 path => '.ssh'
deec7cc4 43 ),
44 '.ssh/authorized_keys' => My::PathStatus->new(
45 path => '.ssh/authorized_keys'
71217e42 46 )
47);
48
9a4942da 49my %path_status;
50
51my $solver = DX::Solver->new(
52 facts => { path_status => DX::SetOver->new(
53 over => 'path',
54 values => \%path_status,
55 ) },
56);
57
734376d9 58$solver->add_rule(@$_) for (
9a4942da 59 [ path_status => [ qw(PS) ],
e183503f 60 [ member_of => 'PS', \'path_status' ] ],
9a4942da 61 [ path => [ qw(PS P) ],
e183503f 62 [ prop => 'PS', \'path', 'P' ] ],
63 [ info_prop => [ qw(PS N V) ],
859049a3 64 [ exists => [ qw(PSI) ],
e183503f 65 [ prop => 'PS', \'info', 'PSI' ],
66 [ prop => 'PSI', 'N', 'V' ] ] ],
67 [ mode => [ qw(PS M) ],
68 [ info_prop => 'PS', \'mode', 'M' ] ],
71217e42 69 [ exists_path => [ qw(PS) ],
e183503f 70 [ info_prop => 'PS', \'is_directory', \1 ] ],
859049a3 71 [ exists_path => [ qw(PS) ],
e183503f 72 [ info_prop => 'PS', \'is_file', \1 ] ],
9a4942da 73 [ is_directory => [ qw(PS) ],
e183503f 74 [ info_prop => 'PS', \'is_directory', \1 ] ],
9a4942da 75 [ is_file => [ qw(PS) ],
e183503f 76 [ info_prop => 'PS', \'is_file', \1 ] ],
9a4942da 77);
78
9a4942da 79%path_status = %protos;
80
5ef4d923 81sub paths_for_simple {
deec7cc4 82 join ' ', map $_->value_for('PS')->path, $solver->query(
734376d9 83 [ qw(PS) ], [ path_status => 'PS' ], @_
9a4942da 84 )->results;
85}
86
5ef4d923 87is(paths_for_simple(), '.ssh .ssh/authorized_keys');
9a4942da 88
5ef4d923 89is(paths_for_simple([ is_directory => 'PS' ]), '.ssh');
9a4942da 90
5ef4d923 91is(paths_for_simple([ is_file => 'PS' ]), '.ssh/authorized_keys');
9a4942da 92
5ef4d923 93is(paths_for_simple([ mode => 'PS', [ value => '0755' ] ]), '.ssh');
734376d9 94
95$solver->add_rule(
96 path_status_at => [ 'PS', 'P' ],
97 [ path_status => 'PS' ],
98 [ path => qw(PS P) ],
99);
100$solver->add_rule(
101 path_status_at => [ 'PS', 'P' ],
102 [ constrain => [] => sub { die "ARGH" } ]
103);
104
105throws_ok {
106 $solver->query(
107 [ qw(PS) ],
108 [ path_status_at => 'PS', [ value => '.ssh' ] ]
109 )->results
110} qr/ARGH/;
111
112delete $solver->rule_set->rules->{'path_status_at/2'};
113
114$solver->add_rule(
115 path_status_at => [ 'PS', 'P' ],
116 [ path_status => 'PS' ],
117 [ path => qw(PS P) ],
118 [ 'cut' ],
119);
120$solver->add_rule(
121 path_status_at => [ 'PS', 'P' ],
122 [ constrain => [] => sub { die "ARGH" } ]
123);
124
125my @res;
126
127lives_ok {
128 @res = $solver->query(
129 [ qw(PS) ],
130 [ path_status_at => 'PS', [ value => '.ssh' ] ]
131 )->results
132};
133
deec7cc4 134is(join(' ', map $_->value_for('PS')->path, @res), '.ssh');
734376d9 135
5ef4d923 136delete $solver->rule_set->rules->{'path_status_at/2'};
137
138$solver->add_rule(
139 path_status_at => [ 'PS', 'P' ],
140 [ path_status => 'PS' ],
141 [ path => qw(PS P) ],
142 [ 'cut' ],
143);
144
145my %ob_res;
146
147$solver->add_rule(
148 path_status_at => [ 'PS', 'P' ],
149 [ observe => [ 'P' ],
150 sub {
151 my ($path) = $_[0];
152 DX::Observer::FromCode->new(
153 code => sub { (path_status => $ob_res{$path}) }
154 )
155 }
156 ],
157 [ path_status => 'PS' ],
158 [ path => qw(PS P) ],
159);
160
71217e42 161%path_status = ();
5ef4d923 162
163$ob_res{'.ssh'} = $protos{'.ssh'};
164
165sub paths_for {
deec7cc4 166 join ' ', map $_->value_for('PS')->path, $solver->query([ 'PS' ], @_)->results;
5ef4d923 167}
168
169is(
170 paths_for([ path_status => 'PS' ], [ path => 'PS', [ value => '.ssh' ] ]),
171 '',
172 'no .ssh entry'
173);
174
175throws_ok { paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]) }
176 qr/refused/;
177
178$solver->{observation_policy} = sub { 1 };
179
180is(
181 paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]),
182 '.ssh',
183 'observation'
184);
185
186is($path_status{'.ssh'}, $ob_res{'.ssh'});
187
188delete $solver->{observation_policy};
189
190lives_ok { paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]) }
191 'No observation required anymore';
734376d9 192
71217e42 193$path_status{'.ssh/authorized_keys'} = $protos{'.ssh/authorized_keys'};
194
195is(
196 paths_for([ path_status => 'PS' ], [ not => [ is_directory => 'PS' ] ]),
197 '.ssh/authorized_keys',
198 'Negation'
199);
200
201$solver->add_rule(@$_) for (
202 [ directory_at => [ qw(PS P) ],
203 [ path_status_at => qw(PS P) ],
204 [ is_directory => 'PS' ] ],
deec7cc4 205 [ file_at => [ qw(PS P) ],
206 [ path_status_at => qw(PS P) ],
207 [ is_file => 'PS' ] ],
71217e42 208);
209
210%path_status = ();
211
212$ob_res{'.ssh'} = $empty{'.ssh'};
213
214#%path_status = %protos;
215
216$solver->{observation_policy} = sub { 1 };
217
218sub dot_ssh_query {
219 $solver->query([ 'PS' ], [ directory_at => 'PS' => [ value => '.ssh' ] ]);
220}
221
222is_deeply(
223 [ dot_ssh_query()->results ],
224 []
225);
226
227#::Dwarn(paths_for([ directory_at => 'PS', [ value => '.ssh' ] ]));
228
229$solver->add_rule(@$_) for (
230 [ is_directory => [ qw(PS) ],
231 [ not => [ exists_path => 'PS' ] ],
232 [ act => [ 'PS' ],
233 sub {
234 my ($ps_var) = @_;
235 my ($id, $value) = ($ps_var->id, $ps_var->bound_value);
236 DX::Action::FromCode->new(
237 expect => sub {
238 ($id => My::PathStatus->new(
239 path => $value->path,
240 info => My::PathStatusInfo->new(
241 is_directory => 1, mode => ''
242 )
243 ))
244 },
245 perform => sub {
246 $ob_res{$value->path} = $protos{$value->path};
247 (path_status => $value);
248 }
249 )
250 } ] ]
251);
252
253%path_status = ();
254
255@res = dot_ssh_query()->results;
256
257is(scalar(@res),1,'Single result');
258
259is($path_status{'.ssh'}, $empty{'.ssh'}, 'Empty observed');
260
deec7cc4 261is(
262 scalar(my ($action) = $res[0]->actions), 1
263);
71217e42 264
e7117efc 265$solver->run_action($action);
71217e42 266
267ok(!$path_status{'.ssh'}, 'Empty retracted');
268
269@res = dot_ssh_query()->results;
270
271is(scalar(@res),1,'Single result');
272
273is($path_status{'.ssh'}, $protos{'.ssh'}, 'Created observed');
274
deec7cc4 275ok(!$res[0]->actions, 'No action');
276
277$solver->add_rule(@$_) for (
278 [ catfile => [ qw(DirPath FileName FilePath) ],
279 DX::Op::FromCode->new(code => sub {
280 my ($self, $state) = @_;
281 my ($dir_path, $file_name, $file_path)
282 = map $state->scope_var($_), qw(DirPath FileName FilePath);
283 die "No." unless $dir_path->is_bound;
284 die "No." unless $file_name->is_bound;
285 die "No." if $file_path->is_bound;
286 my $cat_file = File::Spec->catfile(
287 map $_->bound_value, $dir_path, $file_name
288 );
289 $state->bind_value($file_path->id, $cat_file)
290 ->add_dependencies(
291 $file_path->id => $dir_path->id,
292 $file_path->id => $file_name->id,
293 )
294 ->then($self->next);
295 }) ],
296 [ file_in => [ qw(DirStatus FileName FileStatus) ],
297 [ is_directory => qw(DirStatus) ],
298 [ exists => [ qw(DirPath) ],
299 [ path => qw(DirStatus DirPath) ],
300 [ exists => [ qw(FilePath) ],
301 [ catfile => qw(DirPath FileName FilePath) ],
302 [ file_at => qw(FileStatus FilePath) ] ] ] ],
303 [ is_file => [ qw(PS) ],
304 [ not => [ exists_path => 'PS' ] ],
305 [ act => [ 'PS' ],
306 sub {
307 my ($ps_var) = @_;
308 my ($id, $value) = ($ps_var->id, $ps_var->bound_value);
309 DX::Action::FromCode->new(
310 expect => sub {
311 ($id => My::PathStatus->new(
312 path => $value->path,
313 info => My::PathStatusInfo->new(
314 is_file => 1, mode => ''
315 )
316 ))
317 },
318 perform => sub {
319 $ob_res{$value->path} = $protos{$value->path};
320 (path_status => $value);
321 }
322 )
323 } ] ]
324);
325
326%path_status = ();
327%ob_res = %empty;
328
329sub keys_file {
330 $solver->query([ qw(D F) ],
331 [ directory_at => 'D' => \'.ssh' ],
332 [ file_in => 'D' => \'authorized_keys' => 'F' ],
333 );
334}
335
336@res = keys_file()->results;
337
338is(scalar @res, 1, 'One result');
339
340is(scalar(my @act = $res[0]->actions), 2, 'Two actions');
341
342is(scalar(my ($poss) = grep !@{$_->dependencies}, @act), 1, 'One possible');
343
e7117efc 344$solver->run_action($poss);
deec7cc4 345
346@res = keys_file()->results;
347
348is(scalar @res, 1, 'One result');
349
350is(
351 scalar(($poss) = grep !@{$_->dependencies}, $res[0]->actions), 1,
352 'One possible'
353);
354
e7117efc 355$solver->run_action($poss);
deec7cc4 356
357@res = keys_file()->results;
358
359is(scalar @res, 1, 'One result');
360
361is(scalar($res[0]->actions), 0, 'No actions');
71217e42 362
734376d9 363done_testing;